¿Qué son los agregados y POD y cómo / por qué son especiales?

Esta pregunta frecuente es sobre agregados y POD y cubre el siguiente material:

  • ¿Qué son agregados ?
  • ¿Qué son los POD (datos antiguos simples)?
  • ¿Como están relacionados?
  • ¿Cómo y por qué son especiales?
  • ¿Qué cambios para C ++ 11?

Cómo leer:

Este artículo es bastante largo. Si desea conocer tanto los agregados como los POD (datos antiguos sin formato), tómese un tiempo y léalos. Si le interesan solo los agregados, lea solo la primera parte. Si solo le interesan los POD, primero debe leer la definición, las implicaciones y los ejemplos de agregados, y luego puede pasar a los POD, pero aún así le recomendaría leer la primera parte en su totalidad. La noción de agregados es esencial para definir los POD. Si encuentra algún error (incluso menor, incluida la gramática, la estilística, el formato, la syntax, etc.) deje un comentario, lo editaré.

Qué son los agregados y por qué son especiales

Definición formal del estándar de C ++ ( C ++ 03 8.5.1 §1 ) :

Un agregado es una matriz o una clase (cláusula 9) sin constructores declarados por el usuario (12.1), sin miembros de datos no estáticos protegidos o privados (cláusula 11), sin clases base (cláusula 10) y sin funciones virtuales (10.3 )

Entonces, OK, vamos a analizar esta definición. En primer lugar, cualquier matriz es un agregado. Una clase también puede ser un agregado si … ¡espera! no se dice nada sobre las estructuras o uniones, ¿no pueden ser agregados? Sí pueden. En C ++, el término class refiere a todas las clases, estructuras y uniones. Entonces, una clase (o estructura, o unión) es un agregado si y solo si satisface los criterios de las definiciones anteriores. ¿Qué implican estos criterios?

  • Esto no significa que una clase agregada no pueda tener constructores, de hecho puede tener un constructor predeterminado y / o un constructor de copia, siempre que el comstackdor los declare implícitamente y no explícitamente por el usuario.

  • No hay miembros de datos no estáticos privados o protegidos. Puede tener tantas funciones miembro privadas y protegidas (pero no constructores) como tantos miembros de datos estáticos privados o protegidos y funciones miembro como quiera y no violar las reglas para las clases agregadas.

  • Una clase agregada puede tener un operador y / o destructor de asignación de copiado / usuario definido / definido por el usuario

  • Una matriz es un agregado incluso si es una matriz de tipo de clase no agregada.

Ahora veamos algunos ejemplos:

 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 }; 

Entiendes la idea. Ahora veamos cómo los agregados son especiales. Ellos, a diferencia de las clases no agregadas, se pueden inicializar con llaves {} . Esta syntax de inicialización es comúnmente conocida para las matrices, y acabamos de enterarnos de que son agregados. Entonces, comencemos con ellos.

Type array_name[n] = {a 1 , a 2 , …, a m };

si (m == n)
el i- ésimo elemento de la matriz se inicializa con un i
else si (m
los primeros m elementos de la matriz se inicializan con 1 , a 2 , …, a m y los otros elementos n - m son, si es posible, inicializados en valores (ver más abajo la explicación del término)
de lo contrario, si (m> n)
el comstackdor emitirá un error
else (este es el caso cuando n no se especifica en absoluto como int a[] = {1, 2, 3}; )
el tamaño de la matriz (n) se supone que es igual a m, entonces int a[] = {1, 2, 3}; es equivalente a int a[3] = {1, 2, 3};

Cuando un objeto de tipo escalar ( bool , int , char , double , punteros, etc.) se inicializa en valor , significa que se inicializa con 0 para ese tipo ( false para bool , 0.0 para double , etc.). Cuando se inicializa el valor de un objeto de clase con un constructor predeterminado declarado por el usuario, se invoca su constructor predeterminado. Si el constructor predeterminado está implícitamente definido, todos los miembros no estáticos se inicializan de forma recursiva. Esta definición es imprecisa y un poco incorrecta, pero debería darle la idea básica. Una referencia no se puede inicializar con valores. La inicialización de valor para una clase no agregada puede fallar si, por ejemplo, la clase no tiene un constructor predeterminado apropiado.

Ejemplos de inicialización 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 } 

Ahora veamos cómo las clases agregadas se pueden inicializar con llaves. Más o menos de la misma manera. En lugar de los elementos de la matriz inicializaremos los miembros de datos no estáticos en el orden de su aparición en la definición de la clase (todos son públicos por definición). Si hay menos inicializadores que miembros, el rest se inicializan en valores. Si es imposible inicializar el valor de uno de los miembros que no se inicializaron explícitamente, obtenemos un error en tiempo de comstackción. Si hay más inicializadores de los necesarios, obtenemos también un error en tiempo de comstackción.

 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}}; 

En el ejemplo anterior, yc se inicializa con 'a' , yxi1 con 10 , yxi2 con 20 , yi[0] con 20 , yi[1] con 30 yf se inicializa en valor, es decir, se inicializa con 0.0 . El miembro estático protegido d no se inicializa en absoluto, porque es static .

Las uniones agregadas son diferentes, ya que puede inicializar solo a su primer miembro con llaves. Creo que si estás lo suficientemente avanzado en C ++ como para considerar incluso el uso de uniones (su uso puede ser muy peligroso y debes pensarlo cuidadosamente), puedes buscar las reglas para los sindicatos en el estándar tú mismo :).

Ahora que sabemos qué tienen de especial los agregados, intentemos comprender las restricciones en las clases; Por eso están ahí. Deberíamos entender que la inicialización de miembros con llaves implica que la clase no es más que la sum de sus miembros. Si un constructor definido por el usuario está presente, significa que el usuario necesita hacer un trabajo adicional para inicializar los miembros, por lo que la inicialización del corsé sería incorrecta. Si las funciones virtuales están presentes, significa que los objetos de esta clase tienen (en la mayoría de las implementaciones) un puntero a la llamada vtable de la clase, que se establece en el constructor, por lo que la inicialización de llaves no sería suficiente. Podría averiguar el rest de las restricciones de forma similar a un ejercicio :).

Demasiado sobre los agregados. Ahora podemos definir un conjunto más estricto de tipos, a saber, PODs

¿Qué son los POD y por qué son especiales?

Definición formal del estándar de C ++ ( C ++ 03 9 §4 ) :

Una POD-struct es una clase agregada que no tiene miembros de datos no estáticos de tipo no POD-struct, no POD-union (o matriz de tales tipos) o referencia, y no tiene ningún operador de asignación de copia definido por el usuario ni destructor definido por el usuario. De manera similar, una unión POD es una unión agregada que no tiene miembros de datos no estáticos de tipo no POD-struct, no POD-union (o matriz de tales tipos) o referencia, y no tiene un operador de asignación de copias definido por el usuario y ningún destructor definido por el usuario. Una clase POD es una clase que es una POD-struct o una POD-union.

Wow, este es más difícil de analizar, ¿no? 🙂 Dejemos a uniones afuera (por los mismos motivos que arriba) y reformulemos de una manera un poco más clara:

Una clase agregada se denomina POD si no tiene un destructor y operador de asignación de copia definido por el usuario y ninguno de sus miembros no estáticos es una clase no POD, una matriz de no POD o una referencia.

¿Qué implica esta definición? (¿Mencioné que POD significa Plain Old Data ?)

  • Todas las clases de POD son agregados o, para decirlo de otro modo, si una clase no es un agregado, entonces no es un POD.
  • Las clases, al igual que las estructuras, pueden ser POD aunque el término estándar es POD-struct para ambos casos
  • Al igual que en el caso de los agregados, no importa qué miembros estáticos tenga la clase

Ejemplos:

 struct POD { int x; char y; void f() {} //no harm if there's a function static std::vector v; //static members do not matter }; struct AggregateButNotPOD1 { int x; ~AggregateButNotPOD1() {} //user-defined destructor }; struct AggregateButNotPOD2 { AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class }; 

Las clases POD, POD-unions, escalares y arrays de dichos tipos se denominan colectivamente POD-types.
Los POD son especiales de muchas maneras. Proporcionaré solo algunos ejemplos.

  • Las clases de POD son las más cercanas a las estructuras de C. A diferencia de ellos, los POD pueden tener funciones miembro y miembros estáticos arbitrarios, pero ninguno de estos dos cambia el diseño de la memoria del objeto. Por lo tanto, si desea escribir una biblioteca dinámica más o menos portátil que pueda usarse desde C e incluso .NET, debe intentar hacer que todas las funciones exportadas tomen y devuelvan solo los parámetros de los tipos de POD.

  • La duración de los objetos del tipo de clase no POD comienza cuando el constructor ha finalizado y finaliza cuando el destructor ha finalizado. Para las clases POD, la vida útil comienza cuando el almacenamiento para el objeto está ocupado y finaliza cuando ese almacenamiento se libera o reutiliza.

  • Para objetos de tipos POD, está garantizado por el estándar que cuando usted memcpy los contenidos de su objeto en una matriz de char o unsigned char, y luego memcpy los contenidos en su objeto, el objeto mantendrá su valor original. Tenga en cuenta que no existe tal garantía para objetos de tipos no POD. Además, puede copiar objetos POD de forma segura con memcpy . El siguiente ejemplo asume que T es un tipo de 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 
  • statement goto. Como ya sabrá, es ilegal (el comstackdor debería emitir un error) hacer un salto a través de goto desde un punto en el que alguna variable aún no estaba en el scope hasta un punto donde ya está dentro del scope. Esta restricción se aplica solo si la variable es de tipo no POD. En el siguiente ejemplo, f() está mal formado mientras que g() está bien formado. Tenga en cuenta que el comstackdor de Microsoft es demasiado liberal con esta regla; solo emite una advertencia en ambos 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; } 
  • Se garantiza que no habrá relleno al comienzo de un objeto POD. En otras palabras, si el primer miembro de un POD-clase A es de tipo T, puede reinterpret_cast manera segura de A* a T* y obtener el puntero al primer miembro y viceversa.

La lista sigue y sigue…

Conclusión

Es importante entender qué es exactamente un POD porque muchas características del lenguaje, como ve, se comportan de manera diferente.

¿Qué cambios para C ++ 11?

Agregados

La definición estándar de un agregado ha cambiado ligeramente, pero sigue siendo más o menos el mismo:

Un agregado es una matriz o una clase (Cláusula 9) sin constructores proporcionados por el usuario (12.1), sin inicializadores ortográficos para miembros de datos no estáticos (9.2), sin miembros de datos no estáticos protegidos o privados ( Cláusula 11), sin clases base (Cláusula 10) y sin funciones virtuales (10.3).

Ok, ¿qué cambió?

  1. Anteriormente, un agregado no podía tener constructores declarados por el usuario , pero ahora no puede tener constructores proporcionados por el usuario . ¿Hay una diferencia? Sí, lo hay, porque ahora puedes declarar constructores y establecerlos por defecto :

     struct Aggregate { Aggregate() = default; // asks the compiler to generate the default implementation }; 

    Esto todavía es un agregado porque un constructor (o cualquier función de miembro especial) que está predeterminado en la primera statement no es proporcionado por el usuario.

  2. Ahora, un agregado no puede tener ningún inicializador ortogonal o igual para los miembros de datos no estáticos. ¿Qué significa esto? Bueno, esto es solo porque con este nuevo estándar, podemos inicializar miembros directamente en la clase de esta manera:

     struct NotAggregate { int x = 5; // valid in C++11 std::vector s{1,2,3}; // also valid }; 

    El uso de esta característica hace que la clase ya no sea un agregado porque es básicamente equivalente a proporcionar su propio constructor predeterminado.

Entonces, qué es un agregado no cambió mucho en absoluto. Sigue siendo la misma idea básica, adaptada a las nuevas características.

¿Qué hay de los POD?

Los POD sufrieron muchos cambios. Muchas de las reglas anteriores sobre POD se relajaron en este nuevo estándar, y la forma en que se proporciona la definición en el estándar cambió radicalmente.

La idea de un POD es capturar básicamente dos propiedades distintas:

  1. Es compatible con la inicialización estática, y
  2. Comstackr un POD en C ++ le da el mismo diseño de memoria que una estructura comstackda en C.

Debido a esto, la definición se ha dividido en dos conceptos distintos: clases triviales y clases de diseño estándar , porque son más útiles que POD. El estándar ahora raramente usa el término POD, prefiriendo los conceptos más triviales y de diseño estándar .

La nueva definición básicamente dice que un POD es una clase que es a la vez trivial y tiene un diseño estándar, y esta propiedad debe mantenerse recursivamente para todos los miembros de datos no estáticos:

Una estructura POD es una clase no sindical que es tanto una clase trivial como una clase de diseño estándar, y no tiene miembros de datos no estáticos de tipo estructura no POD, unión no POD (o matriz de tales tipos). De manera similar, una unión POD es una unión que es a la vez una clase trivial y una clase de diseño estándar, y no tiene miembros de datos no estáticos de tipo estructura no POD, unión no POD (o matriz de tales tipos). Una clase POD es una clase que es una estructura POD o una unión POD.

Repasemos cada una de estas dos propiedades en detalle por separado.

Clases triviales

Trivial es la primera propiedad mencionada anteriormente: las clases triviales admiten la inicialización estática. Si una clase es trivialmente copiable (un superconjunto de clases triviales), está bien copiar su representación sobre el lugar con cosas como memcpy y esperar que el resultado sea el mismo.

El estándar define una clase trivial de la siguiente manera:

Una clase trivialmente copiable es una clase que:

– no tiene constructores de copia no triviales (12.8),

– no tiene constructores de movimientos no triviales (12.8),

– no tiene operadores de asignación de copias no triviales (13.5.3, 12.8),

– no tiene operadores de asignación de movimiento no triviales (13.5.3, 12.8), y

– tiene un destructor trivial (12.4).

Una clase trivial es una clase que tiene un constructor predeterminado trivial (12.1) y se puede copiar trivialmente.

[ Nota: en particular, una clase trivialmente copiable o trivial no tiene funciones virtuales o clases base virtuales. -Finalizar nota ]

Entonces, ¿qué son todas esas cosas triviales y no triviales?

Un constructor de copia / movimiento para la clase X es trivial si no es proporcionado por el usuario y si

– la clase X no tiene funciones virtuales (10.3) ni clases base virtuales (10.1), y

– el constructor seleccionado para copiar / mover cada subobjeto directo de clase base es trivial, y

– para cada miembro de datos no estáticos de X que sea del tipo de clase (o matriz del mismo), el constructor seleccionado para copiar / mover ese miembro es trivial;

de lo contrario, el constructor copiar / mover no es trivial.

Básicamente, esto significa que un constructor de copia o movimiento es trivial si no lo proporciona el usuario, la clase no tiene nada virtual, y esta propiedad se mantiene recursivamente para todos los miembros de la clase y para la clase base.

La definición de un operador de asignación de copia / movimiento trivial es muy similar, simplemente reemplazando la palabra “constructor” por “operador de asignación”.

Un destructor trivial también tiene una definición similar, con la restricción añadida de que no puede ser virtual.

Y aún existe otra regla similar para los constructores triviales por defecto, con la adición de que un constructor predeterminado no es trivial si la clase tiene miembros de datos no estáticos con llaves o igual inicializadores , que hemos visto anteriormente.

Aquí hay algunos ejemplos para aclarar todo:

 // 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 }; 

Diseño estándar

El diseño estándar es la segunda propiedad. La norma menciona que estos son útiles para comunicarse con otros idiomas, y eso se debe a que una clase de diseño estándar tiene el mismo diseño de memoria de la estructura o unión C equivalente.

Esta es otra propiedad que debe ser recursiva para los miembros y todas las clases base. Y, como siempre, no se permiten funciones virtuales ni clases base virtuales. Eso haría que el diseño sea incompatible con C.

Una regla relajada aquí es que las clases de diseño estándar deben tener todos los miembros de datos no estáticos con el mismo control de acceso. Anteriormente, estos tenían que ser todos públicos , pero ahora puedes hacerlos privados o protegidos, siempre y cuando sean privados o estén todos protegidos.

Al usar herencia, solo una clase en el árbol de herencia completo puede tener miembros de datos no estáticos, y el primer miembro de datos no estáticos no puede ser de un tipo de clase base (esto podría romper las reglas de aliasing); de lo contrario, no es un estándar clase de diseño.

Así es como va la definición en el texto estándar:

Una clase de diseño estándar es una clase que:

– no tiene miembros de datos no estáticos de tipo clase de diseño no estándar (o matriz de tales tipos) o referencia,

– no tiene funciones virtuales (10.3) ni clases base virtuales (10.1),

– tiene el mismo control de acceso (Cláusula 11) para todos los miembros de datos no estáticos,

– no tiene clases base de diseño no estándar,

– no tiene miembros de datos no estáticos en la clase más derivada y, como máximo, una clase base con miembros de datos no estáticos, o no tiene clases base con miembros de datos no estáticos, y

– no tiene clases base del mismo tipo que el primer miembro de datos no estáticos.

Una estructura de disposición estándar es una clase de diseño estándar definida con la estructura de clave de clase o la clase de clave de clase.

Una unión de diseño estándar es una clase de diseño estándar definida con la unión de clave de clase.

[ Nota: las clases de diseño estándar son útiles para comunicarse con el código escrito en otros lenguajes de progtwigción. Su diseño se especifica en 9.2. -Finalizar nota ]

Y veamos algunos ejemplos.

 // 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 

Conclusión

Con estas nuevas reglas, muchos más tipos pueden ser POD ahora. E incluso si un tipo no es POD, podemos aprovechar algunas de las propiedades POD por separado (si es solo una de diseño trivial o estándar).

La biblioteca estándar tiene características para probar estas propiedades en el encabezado :

 template  struct std::is_pod; template  struct std::is_trivial; template  struct std::is_trivially_copyable; template  struct std::is_standard_layout; 

Qué ha cambiado para C ++ 14

Podemos referirnos al estándar Draft C ++ 14 para referencia.

Agregados

Esto se trata en la sección 8.5.1 Agregados, que nos da la siguiente definición:

Un agregado es una matriz o una clase (Cláusula 9) sin constructores proporcionados por el usuario (12.1), sin miembros de datos no estáticos protegidos o privados (Cláusula 11), sin clases base (Cláusula 10) y sin funciones virtuales (10.3 )

El único cambio ahora es que la adición de los inicializadores de miembro en clase no hace que una clase no sea agregada. Por lo tanto, el siguiente ejemplo de la inicialización agregada de C ++ 11 para las clases con inicializadores in-pace de miembros :

 struct A { int a = 3; int b = 3; }; 

no era un agregado en C ++ 11 pero está en C ++ 14. Este cambio se trata en N3605: Inicializadores y agregados de miembros , que tiene el siguiente resumen:

Bjarne Stroustrup y Richard Smith plantearon un problema acerca de la inicialización agregada y la inicialización de miembros que no funcionaba en conjunto. Este documento propone solucionar el problema mediante la adopción de la redacción propuesta por Smith que elimina la restricción de que los agregados no pueden tener iniciadores de miembros.

POD permanece igual

La definición de estructura POD ( datos antiguos simples ) se trata en la sección 9 Clases que dice:

Una POD struct 110 es una clase sin unión que es a la vez una clase trivial y una clase de diseño estándar, y no tiene miembros de datos no estáticos de tipo estructura no POD, unión no POD (o matriz de tales tipos). De manera similar, una unión POD es una unión que es a la vez una clase trivial y una clase de diseño estándar, y no tiene miembros de datos no estáticos de tipo estructura no POD, unión no POD (o matriz de tales tipos). Una clase POD es una clase que es una estructura POD o una unión POD.

que es la misma redacción que C ++ 11.

puede por favor elaborar las siguientes reglas:

Lo intentaré:

a) las clases de diseño estándar deben tener todos los miembros de datos no estáticos con el mismo control de acceso

Eso es simple: todos los miembros de datos no estáticos deben ser public , private o protected . No puedes tener algunos public y otros private .

El razonamiento para ellos va al razonamiento de tener una distinción entre “diseño estándar” y “diseño no estándar” en absoluto. A saber, para dar al comstackdor la libertad de elegir cómo poner cosas en la memoria. No se trata solo de los indicadores de Vtable.

Cuando estandarizaron C ++ en 98, tenían que predecir básicamente cómo las personas lo implementarían. Si bien tenían bastante experiencia en la implementación con varios sabores de C ++, no estaban seguros de las cosas. Entonces decidieron ser cautos: dar a los comstackdores la mayor libertad posible.

Es por eso que la definición de POD en C ++ 98 es muy estricta. Dio a los comstackdores de C ++ una gran latitud en el diseño de miembros para la mayoría de las clases. Básicamente, los tipos de POD estaban destinados a ser casos especiales, algo que específicamente escribiste por una razón.

Cuando se estaba trabajando en C ++ 11, tenían mucha más experiencia con los comstackdores. Y se dieron cuenta de que … los escritores de comstackdores de C ++ son realmente flojos. Tenían toda esta libertad, pero no hicieron nada con eso.

Las reglas del diseño estándar son una práctica común más o menos codificadora: la mayoría de los comstackdores en realidad no tenían que cambiar mucho o implementarlas en absoluto (fuera de tal vez algunas cosas para los rasgos de tipo correspondientes).

Ahora, cuando se trata de public / private , las cosas son diferentes. La libertad de reordenar qué miembros son public frente a private realmente puede importarle al comstackdor, particularmente en las comstackciones de depuración. Y dado que el punto del diseño estándar es que hay compatibilidad con otros lenguajes, no puede tener el diseño diferente en depuración frente a versión.

Luego está el hecho de que realmente no lastima al usuario. Si está haciendo una clase encapsulada, es probable que todos sus miembros de datos sean private todos modos. Por lo general, no expones miembros de datos públicos en tipos totalmente encapsulados. Así que esto solo sería un problema para los pocos usuarios que sí quieren hacer eso, que quieren esa división.

Entonces no es una gran pérdida.

b) solo una clase en el árbol de herencia completo puede tener miembros de datos no estáticos,

El motivo de esto vuelve a explicar por qué estandarizaron el diseño estándar nuevamente: práctica común.

No hay una práctica común cuando se trata de tener dos miembros de un árbol de herencia que realmente almacenan cosas. Algunos ponen la clase base antes que la derivada, otros lo hacen de otra manera. ¿De qué manera ordena a los miembros si provienen de dos clases base? Y así. Los comstackdores difieren mucho en estas preguntas.

Además, gracias a la regla cero / uno / infinito, una vez que diga que puede tener dos clases con miembros, puede decir todas las que quiera. Esto requiere agregar muchas reglas de diseño sobre cómo manejar esto. Tienes que decir cómo funciona la herencia múltiple, qué clases ponen sus datos antes que otras clases, etc. Son muchas reglas, con muy poca ganancia material.

No puede hacer todo lo que no tiene funciones virtuales y un diseño estándar de constructor por defecto.

y el primer miembro de datos no estático no puede ser de un tipo de clase base (esto podría romper las reglas de aliasing).

Realmente no puedo hablar de esto. No estoy lo suficientemente educado en las reglas de aliasing de C ++ para realmente entenderlo. Pero tiene algo que ver con el hecho de que el miembro base compartirá la misma dirección que la clase base. Es decir:

 struct Base {}; struct Derived : Base { Base b; }; Derived d; static_cast(&d) == &d.b; 

Y eso es probablemente en contra de las reglas de aliasing de C ++. De alguna manera.

Sin embargo, considere esto: ¿qué tan útil podría ser realmente la capacidad de hacer esto? Como solo una clase puede tener miembros de datos no estáticos, Derived debe ser esa clase (ya que tiene una Base como miembro). Entonces Base debe estar vacía (de datos). Y si Base está vacía, así como una clase base … ¿por qué tener un miembro de datos en absoluto?

Como Base está vacía, no tiene estado. Entonces, cualquier función miembro no estática hará lo que haga basándose en sus parámetros, no en su puntero.

Entonces otra vez: no hay gran pérdida.

Cambios en C ++ 17

Descargue aquí el borrador final de C ++ 17 International Standard.

Agregados

C ++ 17 expande y mejora los agregados y la inicialización agregada. La biblioteca estándar también ahora incluye una clase de rasgo de tipo std::is_aggregate . Aquí está la definición formal de la sección 11.6.1.1 y 11.6.1.2 (referencias internas elidas):

Un agregado es una matriz o una clase con
– no hay constructores proporcionados por el usuario, explícitos o heredados,
– sin miembros de datos no estáticos privados o protegidos,
– sin funciones virtuales, y
– sin clases base virtuales, privadas o protegidas.
[Nota: la inicialización agregada no permite el acceso a los miembros o constructores de la clase base protegida y privada. -Finalizar nota]
Los elementos de un agregado son:
– para una matriz, los elementos de la matriz para boost el orden de subíndices, o
— for a class, the direct base classes in declaration order, followed by the direct non-static data members that are not members of an anonymous union, in declaration order.

¿Qué cambió?

  1. Aggregates can now have public, non-virtual base classes. Furthermore, it is not a requirement that base classes be aggregates. If they are not aggregates, they are list-initialized.
 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::value ? 'Y' : 'N') << " i1: " << c.i1 << " i2: " << c.i2 << " j: " << cj << " mm: " << cmm << endl; //stdout: is C aggregate?: Y, i1=1 i2=2 j=3 mm=4 
  1. Explicit defaulted constructors are disallowed
 struct D // not an aggregate { int i = 0; D() = default; explicit D(D const&) = default; }; 
  1. Inheriting constructors are disallowed
 struct B1 { int i1; B1() : i1(0) { } }; struct C : B1 // not an aggregate { using B1::B1; }; 

Trivial Classes

The definition of trivial class was reworked in C++17 to address several defects that were not addressed in C++14. The changes were technical in nature. Here is the new definition at 12.0.6 (internal references elided):

A trivially copyable class is a class:
— where each copy constructor, move constructor, copy assignment operator, and move assignment operator is either deleted or trivial,
— that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and
— that has a trivial, non-deleted destructor.
A trivial class is a class that is trivially copyable and has one or more default constructors, all of which are either trivial or deleted and at least one of which is not deleted. [ Note: In particular, a trivially copyable or trivial class does not have virtual functions or virtual base classes.—end note ]

Cambios:

  1. Under C++14, for a class to be trivial, the class could not have any copy/move constructor/assignment operators that were non-trivial. However, then an implicitly declared as defaulted constructor/operator could be non-trivial and yet defined as deleted because, for example, the class contained a subobject of class type that could not be copied/moved. The presence of such non-trivial, defined-as-deleted constructor/operator would cause the whole class to be non-trivial. A similar problem existed with destructors. C++17 clarifies that the presence of such constructor/operators does not cause the class to be non-trivially copyable, hence non-trivial, and that a trivially copyable class must have a trivial, non-deleted destructor. DR1734 , DR1928
  2. C++14 allowed a trivially copyable class, hence a trivial class, to have every copy/move constructor/assignment operator declared as deleted. If such as class was also standard layout, it could, however, be legally copied/moved with std::memcpy . This was a semantic contradiction, because, by defining as deleted all constructor/assignment operators, the creator of the class clearly intended that the class could not be copied/moved, yet the class still met the definition of a trivially copyable class. Hence in C++17 we have a new clause stating that trivially copyable class must have at least one trivial, non-deleted (though not necessarily publicly accessible) copy/move constructor/assignment operator. See N4148 , DR1734
  3. The third technical change concerns a similar problem with default constructors. Under C++14, a class could have trivial default constructors that were implicitly defined as deleted, yet still be a trivial class. The new definition clarifies that a trivial class must have a least one trivial, non-deleted default constructor. See DR1496

Standard-layout Classes

The definition of standard-layout was also reworked to address defect reports. Again the changes were technical in nature. Here is the text from the standard (12.0.7). As before, internal references are elided:

A class S is a standard-layout class if it:
— has no non-static data members of type non-standard-layout class (or array of such types) or reference,
— has no virtual functions and no virtual base classes,
— has the same access control for all non-static data members,
— has no non-standard-layout base classes,
— has at most one base class subobject of any given type,
— has all non-static data members and bit-fields in the class and its base classes first declared in the same class, and
— has no element of the set M(S) of types (defined below) as a base class.108
M(X) is defined as follows:
— If X is a non-union class type with no (possibly inherited) non-static data members, the set M(X) is empty.
— If X is a non-union class type whose first non-static data member has type X0 (where said member may be an anonymous union), the set M(X) consists of X0 and the elements of M(X0).
— If X is a union type, the set M(X) is the union of all M(Ui) and the set containing all Ui, where each Ui is the type of the ith non-static data member of X.
— If X is an array type with element type Xe, the set M(X) consists of Xe and the elements of M(Xe).
— If X is a non-class, non-array type, the set M(X) is empty.
[ Note: M(X) is the set of the types of all non-base-class subobjects that are guaranteed in a standard-layout class to be at a zero offset in X. —end note ]
[Ejemplo:

 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 

-Final ejemplo]
108) This ensures that two subobjects that have the same class type and that belong to the same most derived object are not allocated at the same address.

Cambios:

  1. Clarified that the requirement that only one class in the derivation tree "has" non-static data members refers to a class where such data members are first declared, not classes where they may be inherited, and extended this requirement to non-static bit fields. Also clarified that a standard-layout class "has at most one base class subobject of any given type." See DR1813 , DR1881
  2. The definition of standard-layout has never allowed the type of any base class to be the same type as the first non-static data member. It is to avoid a situation where a data member at offset zero has the same type as any base class. The C++17 standard provides a more rigorous, recursive definition of "the set of the types of all non-base-class subobjects that are guaranteed in a standard-layout class to be at a zero offset" so as to prohibit such types from being the type of any base class. See DR1672 , DR2120 .