Esta FAQ é sobre agregados e PODs e abrange o seguinte material:
- O que são agregados ?
- O que são PODs (dados antigos simples)?
- Como eles estão relacionados?
- Como e por que eles são especiais?
- O que muda para o C ++ 11?
Esta FAQ é sobre agregados e PODs e abrange o seguinte material:
Respostas:
Este artigo é bastante longo. Se você quiser saber sobre agregados e PODs (dados antigos simples), reserve um tempo e leia-o. Se você estiver interessado apenas em agregados, leia apenas a primeira parte. Se você estiver interessado apenas em PODs, deverá primeiro ler a definição, implicações e exemplos de agregados e, em seguida, poderá pular para PODs, mas eu ainda recomendo a leitura da primeira parte na íntegra. A noção de agregados é essencial para a definição de PODs. Se você encontrar algum erro (mesmo menor, incluindo gramática, estilística, formatação, sintaxe etc.), deixe um comentário, eu o edito.
Esta resposta se aplica ao C ++ 03. Para outros padrões C ++, consulte:
Definição formal do padrão C ++ ( C ++ 03 8.5.1 §1 ) :
Um agregado é uma matriz ou uma classe (cláusula 9) sem construtores declarados pelo usuário (12.1), sem membros de dados não estáticos privados ou protegidos (cláusula 11), sem classes base (cláusula 10) e sem funções virtuais (10.3 )
Então, tudo bem, vamos analisar essa definição. Primeiro de tudo, qualquer matriz é um agregado. Uma classe também pode ser agregada se ... espere! nada é dito sobre estruturas ou sindicatos, eles não podem ser agregados? Sim eles podem. Em C ++, o termo class
refere-se a todas as classes, estruturas e uniões. Portanto, uma classe (ou estrutura ou união) é agregada se, e somente se, atender aos critérios das definições acima. O que esses critérios implicam?
Isso não significa que uma classe agregada não possa ter construtores, na verdade, pode ter um construtor padrão e / ou um construtor de cópias, desde que declarados implicitamente pelo compilador e não explicitamente pelo usuário.
Nenhum membro de dados não estático privado ou protegido . Você pode ter quantas funções membro privadas e protegidas (mas não construtores), bem como muitos membros de dados estáticos privados ou protegidos e funções membro conforme desejar e não violar as regras para classes agregadas
Uma classe agregada pode ter um operador e / ou destrutor de atribuição de cópia declarado / definido pelo usuário
Uma matriz é agregada, mesmo que seja uma matriz do tipo de classe não agregada.
Agora vamos ver alguns exemplos:
class NotAggregate1
{
virtual void f() {} //remember? no virtual functions
};
class NotAggregate2
{
int x; //x is private by default and non-static
};
class NotAggregate3
{
public:
NotAggregate3(int) {} //oops, user-defined constructor
};
class Aggregate1
{
public:
NotAggregate1 member1; //ok, public member
Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment
private:
void f() {} // ok, just a private function
};
Você entendeu a ideia. Agora vamos ver como os agregados são especiais. Diferentemente de classes não agregadas, elas podem ser inicializadas com chaves {}
. Essa sintaxe de inicialização é comumente conhecida por matrizes, e acabamos de aprender que elas são agregadas. Então, vamos começar com eles.
Type array_name[n] = {a1, a2, …, am};
se (m == n)
o i- ésimo elemento da matriz é inicializado com um i
else se (m <n)
os primeiros m elementos da matriz são inicializados com 1 , 2 ,…, am e os outrosn - m
elementos são, se possível, inicializados por valor (veja abaixo a explicação do termo);
caso contrário, (m> n)
o compilador emitirá
outro erro (esse é o caso quando n não for especificado de maneira alguma int a[] = {1, 2, 3};
)
o tamanho de a matriz (n) é assumida como igual a m, portantoint a[] = {1, 2, 3};
é equivalente aint a[3] = {1, 2, 3};
Quando um objecto de tipo escalar ( bool
, int
, char
, double
, pontos, etc.) é inicializado-valor , isso significa que é inicializado com 0
para que tipo ( false
para bool
, 0.0
por double
, etc). Quando um objeto do tipo classe com um construtor padrão declarado pelo usuário é inicializado por valor, seu construtor padrão é chamado. Se o construtor padrão for definido implicitamente, todos os membros não estáticos serão recursivamente inicializados por valor. Essa definição é imprecisa e um pouco incorreta, mas deve fornecer a ideia básica. Uma referência não pode ser inicializada por valor. A inicialização de valor para uma classe não agregada pode falhar se, por exemplo, a classe não tiver um construtor padrão apropriado.
Exemplos de inicialização de matriz:
class A
{
public:
A(int) {} //no default constructor
};
class B
{
public:
B() {} //default constructor available
};
int main()
{
A a1[3] = {A(2), A(1), A(14)}; //OK n == m
A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
int Array1[1000] = {0}; //All elements are initialized with 0;
int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
//the elements in this case are not value-initialized, but have indeterminate values
//(unless, of course, Array4 is a global array)
int array[2] = {1, 2, 3, 4}; //ERROR, too many initializers
}
Agora vamos ver como as classes agregadas podem ser inicializadas com chaves. Praticamente da mesma maneira. Em vez dos elementos da matriz, inicializaremos os membros de dados não estáticos na ordem em que aparecem na definição de classe (todos eles são públicos por definição). Se houver menos inicializadores do que membros, o restante será inicializado por valor. Se for impossível inicializar com valor um dos membros que não foram explicitamente inicializados, obteremos um erro em tempo de compilação. Se houver mais inicializadores do que o necessário, também obteremos um erro em tempo de compilação.
struct X
{
int i1;
int i2;
};
struct Y
{
char c;
X x;
int i[2];
float f;
protected:
static double d;
private:
void g(){}
};
Y y = {'a', {10, 20}, {20, 30}};
No exemplo acima, y.c
é inicializado com 'a'
, y.x.i1
com 10
, y.x.i2
com 20
, y.i[0]
com 20
, y.i[1]
com 30
e y.f
é inicializado por valor, ou seja, inicializado com 0.0
. O membro estático protegido d
não é inicializado, porque é static
.
As uniões agregadas são diferentes, pois você pode inicializar apenas o primeiro membro com chaves. Eu acho que se você é avançado o suficiente em C ++ para considerar o uso de uniões (o uso deles pode ser muito perigoso e deve ser considerado com cuidado), você mesmo pode procurar as regras para uniões no padrão :).
Agora que sabemos o que há de especial em agregados, vamos tentar entender as restrições nas classes; ou seja, por que eles estão lá. Devemos entender que a inicialização de membros com chaves implica que a classe nada mais é do que a soma de seus membros. Se um construtor definido pelo usuário estiver presente, isso significa que o usuário precisa executar algum trabalho extra para inicializar os membros, portanto, a inicialização entre chaves estaria incorreta. Se houver funções virtuais, significa que os objetos desta classe têm (na maioria das implementações) um ponteiro para a chamada vtable da classe, que é definida no construtor, portanto, a inicialização entre chaves seria insuficiente. Você pode descobrir o restante das restrições de maneira semelhante a um exercício :).
O suficiente sobre os agregados. Agora podemos definir um conjunto mais estrito de tipos, ou seja, PODs
Definição formal do padrão C ++ ( C ++ 03 9 §4 ) :
Uma estrutura POD é uma classe agregada que não possui membros de dados não estáticos do tipo estrutura não POD, união não POD (ou matriz de tais tipos) ou referência e não possui operador de atribuição de cópia definido pelo usuário nem destruidor definido pelo usuário. Da mesma forma, uma união POD é uma união agregada que não possui membros de dados não estáticos do tipo estrutura não POD, união POD (ou matriz de tais tipos) ou referência e não possui operador de atribuição de cópia definido pelo usuário e nenhum destruidor definido pelo usuário. Uma classe POD é uma classe que é uma estrutura POD ou uma união POD.
Uau, esse é mais difícil de analisar, não é? :) Vamos deixar os sindicatos de fora (pelo mesmo motivo acima) e reformular de uma maneira um pouco mais clara:
Uma classe agregada é chamada de POD se não tiver um operador e destruidor de atribuição de cópia definido pelo usuário e nenhum de seus membros não estáticos for uma classe não-POD, matriz de não-POD ou uma referência.
O que essa definição implica? (Eu mencionei POD significa Plain Old Data ?)
Exemplos:
struct POD
{
int x;
char y;
void f() {} //no harm if there's a function
static std::vector<char> v; //static members do not matter
};
struct AggregateButNotPOD1
{
int x;
~AggregateButNotPOD1() {} //user-defined destructor
};
struct AggregateButNotPOD2
{
AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};
Classes POD, uniões POD, tipos escalares e matrizes desses tipos são chamados coletivamente de tipos POD.
Os PODs são especiais de várias maneiras. Vou fornecer apenas alguns exemplos.
As classes POD são as mais próximas às estruturas C. Diferentemente deles, os PODs podem ter funções membro e membros estáticos arbitrários, mas nenhum desses dois altera o layout da memória do objeto. Portanto, se você deseja escrever uma biblioteca dinâmica mais ou menos portátil que possa ser usada do C e até do .NET, tente fazer com que todas as suas funções exportadas tomem e retornem apenas parâmetros dos tipos POD.
A vida útil dos objetos do tipo não-POD começa quando o construtor termina e termina quando o destruidor termina. Para as classes POD, a vida útil começa quando o armazenamento do objeto é ocupado e termina quando esse armazenamento é liberado ou reutilizado.
Para objetos de tipos POD, é garantido pelo padrão que, quando você memcpy
colocar o conteúdo do seu objeto em uma matriz de caracteres ou caracteres não assinados, e depois memcpy
o conteúdo voltar ao objeto, o objeto manterá seu valor original. Observe que não existe essa garantia para objetos de tipos não POD. Além disso, você pode copiar objetos POD com segurança memcpy
. O exemplo a seguir assume que T é do tipo POD:
#define N sizeof(T)
char buf[N];
T obj; // obj initialized to its original value
memcpy(buf, &obj, N); // between these two calls to memcpy,
// obj might be modified
memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
// holds its original value
declaração goto. Como você deve saber, é ilegal (o compilador deve emitir um erro) saltar via goto de um ponto em que alguma variável ainda não estava no escopo para um ponto em que já está no escopo. Esta restrição se aplica apenas se a variável for do tipo não POD. No exemplo a seguir f()
está mal formado, enquanto g()
está bem formado. Observe que o compilador da Microsoft é muito liberal com essa regra - apenas envia um aviso nos dois casos.
int f()
{
struct NonPOD {NonPOD() {}};
goto label;
NonPOD x;
label:
return 0;
}
int g()
{
struct POD {int i; char c;};
goto label;
POD x;
label:
return 0;
}
É garantido que não haverá preenchimento no início de um objeto POD. Em outras palavras, se um POD de classe de um primeiro membro é do tipo T, você pode com segurança reinterpret_cast
a partir A*
de T*
e obter o ponteiro para o primeiro membro e vice-versa.
A lista continua e continua…
É importante entender o que exatamente é um POD, porque muitos recursos de linguagem, como você vê, se comportam de maneira diferente para eles.
private:
conforme apropriado): struct A { int const a; };
então A()
está bem formado, mesmo que A
a definição padrão do construtor esteja mal formada.
A definição padrão de um agregado mudou um pouco, mas ainda é praticamente a mesma:
Um agregado é uma matriz ou uma classe (Cláusula 9) sem construtores fornecidos pelo usuário (12.1), sem chaves ou inicializadores iguais para membros de dados não estáticos (9.2), sem membros de dados não estáticos privados ou protegidos ( Cláusula 11), sem classes base (Cláusula 10) e sem funções virtuais (10.3).
Ok, o que mudou?
Anteriormente, um agregado não podia ter construtores declarados pelo usuário , mas agora não pode ter construtores fornecidos pelo usuário . Existe alguma diferença? Sim, existe, porque agora você pode declarar construtores e padronizá- los:
struct Aggregate {
Aggregate() = default; // asks the compiler to generate the default implementation
};
Isso ainda é agregado porque um construtor (ou qualquer função de membro especial) padrão da primeira declaração não é fornecido pelo usuário.
Agora, um agregado não pode ter inicializadores entre parênteses ou iguais para membros de dados não estáticos. O que isto significa? Bem, isso é apenas porque, com esse novo padrão, podemos inicializar membros diretamente na classe assim:
struct NotAggregate {
int x = 5; // valid in C++11
std::vector<int> s{1,2,3}; // also valid
};
O uso desse recurso faz com que a classe não seja mais agregada, porque é basicamente equivalente a fornecer seu próprio construtor padrão.
Portanto, o que é um agregado não mudou muito. Ainda é a mesma idéia básica, adaptada aos novos recursos.
Os PODs passaram por muitas mudanças. Muitas regras anteriores sobre PODs foram relaxadas nesse novo padrão e a maneira como a definição é fornecida no padrão foi radicalmente alterada.
A idéia de um POD é capturar basicamente duas propriedades distintas:
Por esse motivo , a definição foi dividida em dois conceitos distintos: classes triviais e classes de layout padrão , porque são mais úteis que o POD. O padrão agora raramente usa o termo POD, preferindo os conceitos triviais e de layout padrão mais específicos .
A nova definição basicamente diz que um POD é uma classe que é trivial e tem layout padrão, e essa propriedade deve ser mantida recursivamente para todos os membros de dados não estáticos:
Uma estrutura POD é uma classe sem união que é uma classe trivial e uma classe de layout padrão e não possui membros de dados não estáticos do tipo estrutura não POD, união não POD (ou matriz desses tipos). Da mesma forma, uma união POD é uma união que é uma classe trivial e uma classe de layout padrão e não possui membros de dados não estáticos do tipo estrutura não-POD, união não-POD (ou matriz de tais tipos). Uma classe POD é uma classe que é uma estrutura POD ou uma união POD.
Vamos examinar cada uma dessas duas propriedades em detalhes separadamente.
Trivial é a primeira propriedade mencionada acima: classes triviais suportam inicialização estática. Se uma classe é trivialmente copiável (um superconjunto de classes triviais), não há problema em copiar sua representação sobre o local com coisas como memcpy
e esperar que o resultado seja o mesmo.
O padrão define uma classe trivial da seguinte maneira:
Uma classe trivialmente copiável é uma classe que:
- não possui construtores de cópias não triviais (12.8),
- não possui construtores de movimentos não triviais (12.8),
- não possui operadores de atribuição de cópias não triviais (13.5.3, 12.8),
- não possui operadores de atribuição de movimento não triviais (13.5.3, 12.8), e
- possui um destruidor trivial (12.4).
Uma classe trivial é uma classe que possui um construtor padrão trivial (12.1) e é trivialmente copiável.
[ Nota: Em particular, uma classe trivialmente copiável ou trivial não possui funções virtuais ou classes base virtuais. - end note ]
Então, o que são todas essas coisas triviais e não triviais?
Um construtor de copiar / mover para a classe X é trivial se não for fornecido pelo usuário e se
- a classe X não possui funções virtuais (10.3) e nenhuma classe base virtual (10.1), e
- o construtor selecionado para copiar / mover cada subobjeto direto da classe base é trivial, e
- para cada membro de dados não estático de X que é do tipo de classe (ou matriz do mesmo), o construtor selecionado para copiar / mover esse membro é trivial;
caso contrário, o construtor copiar / mover não é trivial.
Basicamente, isso significa que um construtor de copiar ou mover é trivial se não for fornecido pelo usuário, a classe não possui nada virtual e essa propriedade é válida recursivamente para todos os membros da classe e para a classe base.
A definição de um operador de atribuição trivial de copiar / mover é muito semelhante, simplesmente substituindo a palavra "construtor" por "operador de atribuição".
Um destruidor trivial também tem uma definição semelhante, com a restrição adicional de que não pode ser virtual.
E ainda existe outra regra semelhante para construtores padrão triviais, com a adição de que um construtor padrão não é trivial se a classe tiver membros de dados não estáticos com inicializadores de chaves ou iguais , como vimos acima.
Aqui estão alguns exemplos para esclarecer tudo:
// empty classes are trivial
struct Trivial1 {};
// all special members are implicit
struct Trivial2 {
int x;
};
struct Trivial3 : Trivial2 { // base class is trivial
Trivial3() = default; // not a user-provided ctor
int y;
};
struct Trivial4 {
public:
int a;
private: // no restrictions on access modifiers
int b;
};
struct Trivial5 {
Trivial1 a;
Trivial2 b;
Trivial3 c;
Trivial4 d;
};
struct Trivial6 {
Trivial2 a[23];
};
struct Trivial7 {
Trivial6 c;
void f(); // it's okay to have non-virtual functions
};
struct Trivial8 {
int x;
static NonTrivial1 y; // no restrictions on static members
};
struct Trivial9 {
Trivial9() = default; // not user-provided
// a regular constructor is okay because we still have default ctor
Trivial9(int x) : x(x) {};
int x;
};
struct NonTrivial1 : Trivial3 {
virtual void f(); // virtual members make non-trivial ctors
};
struct NonTrivial2 {
NonTrivial2() : z(42) {} // user-provided ctor
int z;
};
struct NonTrivial3 {
NonTrivial3(); // user-provided ctor
int w;
};
NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
// still counts as user-provided
struct NonTrivial5 {
virtual ~NonTrivial5(); // virtual destructors are not trivial
};
O layout padrão é a segunda propriedade. O padrão menciona que eles são úteis para a comunicação com outros idiomas, e isso ocorre porque uma classe de layout padrão tem o mesmo layout de memória da estrutura ou união C equivalente.
Essa é outra propriedade que deve conter recursivamente para membros e todas as classes base. E, como sempre, nenhuma função virtual ou classe base virtual é permitida. Isso tornaria o layout incompatível com C.
Uma regra simples aqui é que as classes de layout padrão devem ter todos os membros de dados não estáticos com o mesmo controle de acesso. Anteriormente estes tiveram que ser tudo público , mas agora você pode fazê-los privados ou protegidos, desde que eles são tudo privado ou tudo protegido.
Ao usar herança, apenas uma classe na árvore de herança inteira pode ter membros de dados não estáticos, e o primeiro membro de dados não estáticos não pode ser do tipo de classe base (isso pode violar as regras de alias), caso contrário, não é um padrão. classe de layout.
É assim que a definição é apresentada no texto padrão:
Uma classe de layout padrão é uma classe que:
- não possui membros de dados não estáticos do tipo classe de layout não padrão (ou matriz de tais tipos) ou referência,
- não possui funções virtuais (10.3) e nenhuma classe base virtual (10.1),
- possui o mesmo controle de acesso (cláusula 11) para todos os membros de dados não estáticos,
- não possui classes básicas de layout fora do padrão,
- não possui membros de dados não estáticos na classe mais derivada e no máximo uma classe base com membros de dados não estáticos ou não possui classes de base com membros de dados não estáticos, e
- não possui classes base do mesmo tipo que o primeiro membro de dados não estático.
Uma estrutura de layout padrão é uma classe de layout padrão definida com a estrutura de chave de classe ou a classe de chave de classe.
Uma união de layout padrão é uma classe de layout padrão definida com a união de chave de classe.
[ Nota: As classes de layout padrão são úteis para se comunicar com o código escrito em outras linguagens de programação. Seu layout é especificado em 9.2. - end note ]
E vamos ver alguns exemplos.
// empty classes have standard-layout
struct StandardLayout1 {};
struct StandardLayout2 {
int x;
};
struct StandardLayout3 {
private: // both are private, so it's ok
int x;
int y;
};
struct StandardLayout4 : StandardLayout1 {
int x;
int y;
void f(); // perfectly fine to have non-virtual functions
};
struct StandardLayout5 : StandardLayout1 {
int x;
StandardLayout1 y; // can have members of base type if they're not the first
};
struct StandardLayout6 : StandardLayout1, StandardLayout5 {
// can use multiple inheritance as long only
// one class in the hierarchy has non-static data members
};
struct StandardLayout7 {
int x;
int y;
StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};
struct StandardLayout8 {
public:
StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
int x;
};
struct StandardLayout9 {
int x;
static NonStandardLayout1 y; // no restrictions on static members
};
struct NonStandardLayout1 {
virtual f(); // cannot have virtual functions
};
struct NonStandardLayout2 {
NonStandardLayout1 X; // has non-standard-layout member
};
struct NonStandardLayout3 : StandardLayout1 {
StandardLayout1 x; // first member cannot be of the same type as base
};
struct NonStandardLayout4 : StandardLayout3 {
int z; // more than one class has non-static data members
};
struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class
Com essas novas regras, muito mais tipos podem ser PODs agora. E mesmo que um tipo não seja POD, podemos tirar proveito de algumas das propriedades POD separadamente (se for apenas uma de layout trivial ou padrão).
A biblioteca padrão possui características para testar essas propriedades no cabeçalho <type_traits>
:
template <typename T>
struct std::is_pod;
template <typename T>
struct std::is_trivial;
template <typename T>
struct std::is_trivially_copyable;
template <typename T>
struct std::is_standard_layout;
Podemos nos referir ao padrão Draft C ++ 14 para referência.
Isso é abordado na seção 8.5.1
Agregados, que nos fornece a seguinte definição:
Um agregado é uma matriz ou uma classe (Cláusula 9) sem construtores fornecidos pelo usuário (12.1), sem membros de dados não estáticos privados ou protegidos (Cláusula 11), sem classes base (Cláusula 10) e sem funções virtuais (10.3 )
A única alteração agora é adicionar inicializadores de membros da classe não tornar uma classe não agregada. Portanto, o exemplo a seguir da inicialização agregada do C ++ 11 para classes com inicializadores in-pace de membros :
struct A
{
int a = 3;
int b = 3;
};
não era um agregado no C ++ 11, mas está no C ++ 14. Essa alteração é abordada no N3605: Inicializadores e agregados de membros , com o seguinte resumo:
Bjarne Stroustrup e Richard Smith levantaram uma questão sobre a inicialização agregada e os inicializadores de membros não trabalhando juntos. Este artigo propõe corrigir o problema adotando a redação proposta por Smith que remove uma restrição que os agregados não podem ter inicializadores de membros.
A definição para estrutura POD ( dados antigos simples ) é abordada na seção 9
Classes, que diz:
Uma estrutura POD 110 é uma classe de não união que é uma classe trivial e uma classe de layout padrão e não possui membros de dados não estáticos do tipo estrutura não POD, união não POD (ou matriz de tais tipos). Da mesma forma, uma união POD é uma união que é uma classe trivial e uma classe de layout padrão e não possui membros de dados não estáticos do tipo estrutura não-POD, união não-POD (ou matriz desses tipos). Uma classe POD é uma classe que é uma estrutura POD ou uma união POD.
que tem a mesma redação do C ++ 11.
Conforme observado no pod de comentários, a definição de layout padrão foi alterada para o C ++ 14, mas isso ocorreu por meio de relatórios de defeitos que foram aplicados ao C ++ 14 após o fato.
Havia três DRs:
Portanto , o layout padrão partiu deste Pre C ++ 14:
Uma classe de layout padrão é uma classe que:
- (7.1) não possui membros de dados não estáticos do tipo classe de layout não padrão (ou matriz de tais tipos) ou referência,
- (7.2) não possui funções virtuais ([class.virtual]) e nenhuma classe base virtual ([class.mi]),
- (7.3) tem o mesmo controle de acesso (Cláusula [class.access]) para todos os membros de dados não estáticos,
- (7.4) não possui classes base de layout fora do padrão,
- (7.5) não possui membros de dados não estáticos na classe mais derivada e no máximo uma classe base com membros de dados não estáticos ou não possui classes de base com membros de dados não estáticos e
- (7.6) não possui classes de base do mesmo tipo que o primeiro membro de dados não estático.109
Para isso no C ++ 14 :
Uma classe S é uma classe de layout padrão se:
- (3.1) não possui membros de dados não estáticos do tipo classe de layout não padrão (ou matriz de tais tipos) ou referência,
- (3.2) não possui funções virtuais nem classes base virtuais,
- (3.3) tem o mesmo controle de acesso para todos os membros de dados não estáticos,
- (3.4) não possui classes base de layout fora do padrão,
- (3.5) tem no máximo um subobjeto de classe base de qualquer tipo,
- (3.6) possui todos os membros de dados não estáticos e campos de bits na classe e suas classes base declaradas pela primeira vez na mesma classe, e
- (3.7) não possui nenhum elemento do conjunto M (S) dos tipos como classe base, sendo que para qualquer tipo X, M (X) é definido da seguinte forma.104 [Nota: M (X) é o conjunto dos tipos de todos os subobjetos que não são da classe base que podem estar com um deslocamento zero no X. - nota final]
- (3.7.1) Se X for um tipo de classe sem união, sem membros de dados não estáticos (possivelmente herdados), o conjunto M (X) estará vazio.
- (3.7.2) Se X é um tipo de classe não-união com um membro de dados não estático do tipo X0 que é de tamanho zero ou é o primeiro membro de dados não estático de X (em que o membro pode ser uma união anônima ), o conjunto M (X) consiste em X0 e nos elementos de M (X0).
- (3.7.3) Se X é um tipo de união, o conjunto M (X) é a união de todos os M (Ui) e o conjunto que contém todos os Ui, em que cada Ui é o tipo do i-ésimo membro de dados não estático de X .
- (3.7.4) Se X é um tipo de matriz com o tipo de elemento Xe, o conjunto M (X) consiste em Xe e nos elementos de M (Xe).
- (3.7.5) Se X for do tipo sem classe e sem matriz, o conjunto M (X) estará vazio.
você pode elaborar as seguintes regras:
Vou tentar:
a) as classes de layout padrão devem ter todos os membros de dados não estáticos com o mesmo controle de acesso
Isso é simples: todos os membros de dados não-estáticos devem todos ser public
, private
ou protected
. Você não pode ter alguns public
e alguns private
.
O raciocínio para eles vai para o raciocínio por ter uma distinção entre "layout padrão" e "layout não padrão". Ou seja, para dar ao compilador a liberdade de escolher como colocar as coisas na memória. Não se trata apenas de ponteiros vtable.
Quando eles padronizaram o C ++ em 98, eles precisavam basicamente prever como as pessoas o implementariam. Embora tivessem um pouco de experiência em implementação com vários tipos de C ++, eles não estavam certos sobre as coisas. Então eles decidiram ser cautelosos: dê aos compiladores o máximo de liberdade possível.
É por isso que a definição de POD no C ++ 98 é tão rigorosa. Deu aos compiladores C ++ uma grande latitude no layout dos membros para a maioria das classes. Basicamente, os tipos de POD destinavam-se a casos especiais, algo que você escreveu especificamente por um motivo.
Quando o C ++ 11 estava sendo trabalhado, eles tinham muito mais experiência com compiladores. E eles perceberam que ... os escritores do compilador C ++ são realmente preguiçosos. Eles tinham toda essa liberdade, mas eles não fazem nada com ele.
As regras do layout padrão são mais ou menos codificadoras da prática comum: a maioria dos compiladores realmente não precisou mudar muito, se é que havia alguma coisa para implementá-las (fora de talvez algumas coisas para os traços de tipo correspondentes).
Agora, quando se trata de public
/ private
, as coisas são diferentes. A liberdade de reordenar quais membros são public
vs. private
realmente pode ser importante para o compilador, principalmente nas compilações de depuração. E como o ponto do layout padrão é que há compatibilidade com outros idiomas, não é possível que o layout seja diferente na depuração versus versão.
Depois, há o fato de que realmente não prejudica o usuário. Se você estiver criando uma classe encapsulada, é bem provável que todos os seus membros de dados sejam private
assim. Geralmente, você não expõe membros de dados públicos em tipos totalmente encapsulados. Portanto, isso seria apenas um problema para aqueles poucos usuários que desejam fazer isso, que desejam essa divisão.
Portanto, não é uma grande perda.
b) apenas uma classe em toda a árvore de herança pode ter membros de dados não estáticos,
A razão para este é o motivo pelo qual eles padronizaram o layout padrão novamente: prática comum.
Não há prática comum quando se trata de ter dois membros de uma árvore de herança que realmente armazenam coisas. Alguns colocam a classe base antes da derivada, outros fazem o contrário. De que maneira você ordena os membros se eles vierem de duas classes base? E assim por diante. Os compiladores divergem bastante sobre essas questões.
Além disso, graças à regra zero / um / infinito, quando você disser que pode ter duas classes com membros, poderá dizer quantas quiser. Isso requer adicionar muitas regras de layout para lidar com isso. Você tem que dizer como a herança múltipla funciona, que classes colocam seus dados antes de outras classes, etc. São muitas regras, para pouquíssimo ganho material.
Você não pode criar tudo o que não possui funções virtuais e um layout padrão de construtor padrão.
e o primeiro membro de dados não estático não pode ser de um tipo de classe base (isso pode violar regras de alias).
Eu realmente não posso falar com este. Não sou educado o suficiente nas regras de alias do C ++ para realmente entender. Mas isso tem algo a ver com o fato de o membro base compartilhar o mesmo endereço que a própria classe base. Isso é:
struct Base {};
struct Derived : Base { Base b; };
Derived d;
static_cast<Base*>(&d) == &d.b;
E isso provavelmente é contra as regras de alias do C ++. De algum modo.
No entanto, considere isto: o quão útil podia ter a capacidade de fazer isso nunca realmente ser? Como apenas uma classe pode ter membros de dados não estáticos, Derived
deve ser essa classe (já que ela possui um Base
como membro). Portanto, Base
deve estar vazio (de dados). E se Base
estiver vazio, assim como uma classe base ... por que um membro de dados?
Como Base
está vazio, não possui estado. Portanto, qualquer função membro não estática fará o que fizer com base em seus parâmetros, não em seu this
ponteiro.
Então, novamente: sem grandes perdas.
static_cast<Base*>(&d)
e &d.b
são do mesmo Base*
tipo, eles apontam para coisas diferentes, quebrando a regra de alias. Por favor me corrija.
Derived
deve ser essa classe?
Derived
o primeiro membro de sua classe seja sua classe base, ele deve ter duas coisas: uma classe base e um membro . E como apenas uma classe na hierarquia pode ter membros (e ainda ter layout padrão), isso significa que sua classe base não pode ter membros.
Faça o download do rascunho final do Padrão Internacional C ++ 17 aqui .
Agregados
O C ++ 17 expande e aprimora agregados e inicialização agregada. A biblioteca padrão agora também inclui uma std::is_aggregate
classe de característica de tipo. Aqui está a definição formal das seções 11.6.1.1 e 11.6.1.2 (referências internas elididas):
Um agregado é uma matriz ou uma classe com
- sem construtores fornecidos pelo usuário, explícitos ou herdados
- sem membros de dados não estáticos privados ou protegidos,
- sem funções virtuais e
- sem classes de base virtuais, privadas ou protegidas.
[Nota: A inicialização agregada não permite acessar membros ou construtores da classe base protegida e privada. —End note]
Os elementos de um agregado são:
- para uma matriz, os elementos da matriz em ordem crescente de subscrito ou
- para uma classe, as classes base diretas em ordem de declaração, seguidas pelos membros de dados não estáticos diretos que não são membros de uma união anônima, em ordem de declaração.
O que mudou?
struct B1 // not a aggregate
{
int i1;
B1(int a) : i1(a) { }
};
struct B2
{
int i2;
B2() = default;
};
struct M // not an aggregate
{
int m;
M(int a) : m(a) { }
};
struct C : B1, B2
{
int j;
M m;
C() = default;
};
C c { { 1 }, { 2 }, 3, { 4 } };
cout
<< "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
<< " i1: " << c.i1 << " i2: " << c.i2
<< " j: " << c.j << " m.m: " << c.m.m << endl;
//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
struct D // not an aggregate
{
int i = 0;
D() = default;
explicit D(D const&) = default;
};
struct B1
{
int i1;
B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
using B1::B1;
};
Classes triviais
A definição de classe trivial foi reformulada no C ++ 17 para solucionar vários defeitos que não foram abordados no C ++ 14. As mudanças foram de natureza técnica. Aqui está a nova definição em 12.0.6 (referências internas elididas):
Uma classe trivialmente copiável é uma classe:
- onde cada construtor de cópia, construtor de movimentação, operador de atribuição de cópia e operador de atribuição de movimentação é excluído ou trivial,
- que possui pelo menos um construtor de cópia não excluído, construtor de movimentação, operador de atribuição de cópia, ou mova o operador de atribuição e
- que tenha um destruidor trivial e não excluído.
Uma classe trivial é uma classe que é trivialmente copiável e possui um ou mais construtores padrão, todos triviais ou excluídos e pelo menos um dos quais não é excluído. [Nota: Em particular, uma classe trivialmente copiável ou trivial não possui funções virtuais ou classes base virtuais. - nota final]
Alterar:
std::memcpy
. Isso era uma contradição semântica, porque, ao definir como excluídos todos os operadores de construtor / atribuição, o criador da classe claramente pretendia que a classe não pudesse ser copiada / movida, mas a classe ainda atendia à definição de uma classe trivialmente copiável. Portanto, no C ++ 17, temos uma nova cláusula declarando que a classe trivialmente copiável deve ter pelo menos um operador construtor / atribuição de cópia / movimentação trivial e não excluído (embora não necessariamente acessível ao público). Veja N4148 , DR1734Classes de layout padrão
A definição de layout padrão também foi reformulada para tratar dos relatórios de defeitos. Mais uma vez, as mudanças foram de natureza técnica. Aqui está o texto do padrão (12.0.7). Como antes, as referências internas são elididas:
Uma classe S é uma classe de layout padrão se:
- não possui membros de dados não estáticos do tipo classe de layout não padrão (ou matriz de tais tipos) ou referência,
- não possui funções virtuais nem classes base virtuais,
- possui o mesmo controle de acesso para todos os membros de dados não estáticos,
- não possui classes base de layout não padrão,
- possui no máximo uma subobjeto de classe base de qualquer tipo,
- possui todos os membros de dados não estáticos e campos de bits em a classe e suas classes base declaradas pela primeira vez na mesma classe e
- não possui nenhum elemento do conjunto M (S) dos tipos (definidos abaixo) como classe base.108
M (X) é definido da seguinte forma:
- Se X é um tipo de classe sem união sem ( membros de dados não estáticos possivelmente herdados), o conjunto M (X) está vazio.
- Se X é um tipo de classe não-união cujo primeiro membro de dados não estáticos possui o tipo X0 (onde o referido membro pode ser uma união anônima), o conjunto M (X) consiste em X0 e nos elementos de M (X0).
- Se X é um tipo de união, o conjunto M (X) é a união de todos os M (Ui) e o conjunto contendo todas as Ui, em que cada Ui é o tipo do i-ésimo membro de dados não estáticos de X.
- Se X é um tipo de matriz com o tipo de elemento Xe, o conjunto M (X) consiste em Xe e nos elementos de M (Xe).
- Se X é do tipo sem classe e sem matriz, o conjunto M (X) está vazio.
[Nota: M (X) é o conjunto dos tipos de todos os subobjetos que não são da classe base que são garantidos em uma classe de layout padrão como tendo um deslocamento zero em X. - finalizar nota]
[Exemplo:
struct B { int i; }; // standard-layout class struct C : B { }; // standard-layout class struct D : C { }; // standard-layout class struct E : D { char : 4; }; // not a standard-layout class struct Q {}; struct S : Q { }; struct T : Q { }; struct U : S, T { }; // not a standard-layout class
108 exemplo ) assegura que dois subobjetos que tenham o mesmo tipo de classe e pertençam ao mesmo objeto mais derivado não sejam alocados no mesmo endereço.
Alterar:
Nota: O comitê de padrões do C ++ pretendeu que as alterações acima, com base nos relatórios de defeitos, fossem aplicadas ao C ++ 14, embora o novo idioma não esteja no padrão C ++ 14 publicado. Está no padrão C ++ 17.
Seguindo o resto do tema claro desta questão, o significado e o uso de agregados continuam a mudar com todos os padrões. Existem várias mudanças importantes no horizonte.
No C ++ 17, esse tipo ainda é um agregado:
struct X {
X() = delete;
};
E, portanto, X{}
ainda compila porque é a inicialização agregada - não uma invocação de construtor. Consulte também: Quando um construtor privado não é um construtor privado?
No C ++ 20, a restrição mudará de exigir:
nenhum
explicit
construtor fornecido pelo usuário ou herdado
para
nenhum construtor declarado ou herdado pelo usuário
Isso foi adotado no rascunho de trabalho do C ++ 20 . Nem a questão X
aqui nem a C
questão vinculada serão agregadas em C ++ 20.
Isso também cria um efeito ioiô com o seguinte exemplo:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
No C ++ 11/14, nãoB
era um agregado devido à classe base, portanto, executa a inicialização de valor que chama quais chamadas , em um ponto em que é acessível. Isso foi bem formado.B{}
B::B()
A::A()
No C ++ 17, B
tornou-se agregado porque as classes base eram permitidas, o que fez B{}
a inicialização agregada. Isso requer a inicialização da lista de cópias e A
de {}
, mas fora do contexto de B
, onde não está acessível. No C ++ 17, isso é mal formado (auto x = B();
seria bom).
No C ++ 20 agora, devido à alteração de regra acima, B
mais uma vez deixa de ser um agregado (não por causa da classe base, mas por causa do construtor padrão declarado pelo usuário - mesmo que seja o padrão). Então, voltamos ao B
construtor de, e esse trecho se torna bem formado.
Um problema comum que surge é querer usar emplace()
construtores -style com agregados:
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
Isso não funciona, porque emplace
tentará efetivamente executar a inicialização X(1, 2)
, que não é válida. A solução típica é adicionar um construtor a X
, mas com esta proposta (atualmente trabalhando no Core), os agregados terão efetivamente sintetizados construtores que fazem a coisa certa - e se comportam como construtores regulares. O código acima será compilado como está no C ++ 20.
No C ++ 17, isso não compila:
template <typename T>
struct Point {
T x, y;
};
Point p{1, 2}; // error
Os usuários precisariam escrever seu próprio guia de dedução para todos os modelos agregados:
template <typename T> Point(T, T) -> Point<T>;
Mas como isso é, em certo sentido, "a coisa mais óbvia" a ser feita, e é basicamente apenas um clichê, a linguagem fará isso por você. Este exemplo será compilado no C ++ 20 (sem a necessidade do guia de dedução fornecido pelo usuário).