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





constructor initialize (6)


Lo que ha cambiado para C ++ 14

Podemos referirnos al borrador de norma 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 privados o protegidos (Cláusula 11), sin clases base (Cláusula 10) y sin funciones virtuales (10.3 ).

El único cambio ahora es agregar inicializadores de miembro en clase que no hace que una clase sea un no agregado. Así que el siguiente ejemplo de C ++ 11 agrega inicialización para clases con inicializadores de paso 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 sobre la inicialización agregada y los inicializadores de miembros que no funcionan juntos. Este documento propone solucionar el problema adoptando la redacción propuesta de Smith que elimina una restricción que los agregados no pueden tener inicializadores de miembros.

POD se mantiene igual

La definición de estructura POD ( datos antiguos ) está cubierta en la sección 9 Clases que dice:

Una estructura POD 110 es una clase no sindicalizada 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.

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

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

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



¿Puedes 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 algo public y algo private .

El razonamiento para ellos va al razonamiento para tener una distinción entre "diseño estándar" y "diseño no estándar" en absoluto. A saber, para darle al compilador la libertad de elegir cómo guardar las cosas en la memoria. No se trata solo de punteros vtable.

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

Es por eso que la definición de POD en C ++ 98 es tan estricta. Le dio a los compiladores de C ++ una gran libertad 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 escribiste específicamente por una razón.

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

Las reglas del diseño estándar son más o menos prácticas comunes de codificación: la mayoría de los compiladores no tuvieron que cambiar mucho o nada para implementarlos (aparte de 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 realidad puede ser importante para el compilador, particularmente en las compilaciones de depuración. Y dado que el punto de diseño estándar es que hay compatibilidad con otros idiomas, no se puede hacer que el diseño sea diferente en la depuración en comparación con la versión.

Luego está el hecho de que realmente no hace daño al usuario. Si está haciendo una clase encapsulada, las probabilidades son buenas de que todos los miembros de sus datos serán private todos modos. Por lo general, no expone a los miembros de datos públicos en tipos completamente encapsulados. Entonces, esto solo sería un problema para aquellos pocos usuarios que quieren hacer eso, que quieren esa división.

Así que no es una gran pérdida.

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

La razón de esto se remonta a por qué estandarizaron de nuevo el diseño estándar: la 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 almacena cosas. Algunos ponen la clase base antes que los derivados, otros lo hacen de la otra manera. ¿De qué manera ordenas a los miembros si provienen de dos clases básicas? Y así. Los compiladores divergen mucho en estas preguntas.

Además, gracias a la regla cero / uno / infinito, una vez que digas que puedes tener dos clases con miembros, puedes decir tantas como quieras. Esto requiere agregar muchas reglas de diseño sobre cómo manejar esto. Tiene que decir cómo funciona la herencia múltiple, qué clases colocan sus datos antes que otras clases, etc. Son muchas reglas, con muy poco beneficio material.

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

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).

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

struct Base {};
struct Derived : Base { Base b; };

Derived d;
static_cast<Base*>(&d) == &d.b;

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

Sin embargo, considera esto: ¿qué tan útil podría ser tener la capacidad de hacer esto? Dado que solo una clase puede tener miembros de datos no estáticos, Derived debe ser esa clase (ya que tiene una Base como miembro). Así que la Base debe estar vacía (de datos). Y si la Base está vacía, al igual que una clase base ... ¿por qué un miembro de datos la tiene?

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

Así que de nuevo: no hay gran pérdida.




¿Qué cambios para C ++ 11?

Agregados

La definición estándar de un agregado ha cambiado ligeramente, pero sigue siendo bastante similar:

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

Ok que cambio

  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í, la hay, porque ahora puedes declarar constructores y predeterminarlos :

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

    Esto sigue siendo un agregado porque un constructor (o cualquier función de miembro especial) que está predeterminada en la primera declaración no es proporcionado por el usuario.

  2. Ahora, un agregado no puede tener ningún inicializador de paréntesis o igual para 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<int> 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, lo que es un agregado no cambió mucho en absoluto. Sigue siendo la misma idea básica, adaptada a las nuevas características.

¿Qué pasa con los PODs?

Los POD pasaron por muchos cambios. Muchas de las reglas anteriores sobre POD se relajaron en esta nueva norma, y ​​la forma en que se proporciona la definición en la norma 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. La compilación de un POD en C ++ le brinda el mismo diseño de memoria que una estructura compilada 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 rara vez usa el término POD, prefiriendo los conceptos más específicos triviales y de diseño estándar .

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

Una estructura POD es una clase no sindicalizada 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 en 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 copia no triviales (13.5.3, 12.8),

- no tiene operadores de asignación de movimientos 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 de forma trivial.

[ Nota: En particular, una clase trivialmente copiable o trivial no tiene funciones virtuales o clases base virtuales. "Nota final "

Entonces, ¿cuáles son todas esas cosas triviales y no triviales?

Un constructor de copiar / mover 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 de clase base directa es trivial, y

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

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

Básicamente, esto significa que un constructor de copia o movimiento es trivial si no es proporcionado por el usuario, la clase no tiene nada de 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 sustituyendo 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 predeterminados triviales, con la adición de que un constructor predeterminado no es trivial si la clase tiene miembros de datos no estáticos con inicializadores de refuerzo o iguales , 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. El estándar menciona que son útiles para comunicarse con otros idiomas, y eso es porque una clase de diseño estándar tiene el mismo diseño de memoria de la estructura C o equivalente.

Esta es otra propiedad que debe ser recursiva para los miembros y todas las clases base. Y como de costumbre, no se permiten funciones virtuales o clases de base virtual. 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 puede hacerlos privados o protegidos, siempre y cuando sean todos privados o protegidos.

Cuando se usa la herencia, solo una clase en todo el árbol de herencia 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 diseño 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 códigos escritos en otros lenguajes de programación. Su diseño se especifica en 9.2. "Nota final "

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 de POD por separado (si es solo una de diseño trivial o estándar).

La biblioteca estándar tiene rasgos para probar estas propiedades en el encabezado <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;



Cómo leer:

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

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

Definición formal del estándar 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 privados o protegidos (cláusula 11), sin clases base (cláusula 10) y sin funciones virtuales (10.3) ).

Entonces, OK, analicemos 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 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 cumple con 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 compilador los declare implícitamente y no explícitamente el usuario.

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

  • Una clase agregada puede tener un operador y / o destructor de asignación de copia declarado / 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
};

Tienes la idea Ahora veamos como los agregados son especiales. Ellos, a diferencia de las clases no agregadas, pueden inicializarse con llaves {} . Esta sintaxis de inicialización es comúnmente conocida para matrices, y acabamos de aprender que estos son agregados. Entonces, vamos a empezar con ellos.

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

si (m == n)
El elemento i th de la matriz se inicializa con un i
si no (m <n)
los primeros m elementos de la matriz se inicializan con un 1 , un 2 , ..., un my los otros elementos de n - m , si es posible, el valor se inicializa (consulte la explicación del término a continuación)
más si (m> n)
el compilador 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, por lo que 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 con valores , significa que se inicializa con 0 para ese tipo ( false para bool , 0.0 para double , etc.). Cuando un objeto de tipo de clase con un constructor predeterminado declarado por el usuario se inicializa con valor, se llama a su constructor predeterminado. Si el constructor predeterminado está definido implícitamente, todos los miembros no estáticos se inicializan de forma recursiva. Esta definición es imprecisa y un poco incorrecta, pero debería darle una idea básica. Una referencia no puede ser inicializada por valores. La inicialización del 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 se pueden inicializar las clases agregadas con llaves. Más o menos de la misma manera. En lugar de los elementos del arreglo, inicializaremos los miembros de datos no estáticos en el orden en que aparecen en la definición de la clase (todos son públicos por definición). Si hay menos inicializadores que miembros, el resto se inicializa con valores. Si es imposible inicializar por valor uno de los miembros que no se inicializaron explícitamente, obtenemos un error en tiempo de compilación. Si hay más inicializadores de los necesarios, también obtenemos un error en tiempo de compilació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 y yf se inicializa con valores, 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 en que puede inicializar solo sus primeros miembros con llaves. Creo que si está lo suficientemente avanzado en C ++ para siquiera considerar el uso de uniones (su uso puede ser muy peligroso y debe pensarse con cuidado), puede consultar las reglas para los sindicatos en la norma usted mismo :).

Ahora que sabemos lo que es especial acerca de los agregados, tratemos de entender las restricciones en las clases; Por eso están ahí. Debemos entender que la inicialización con llaves de los miembros implica que la clase no es más que la suma de sus miembros. Si un constructor definido por el usuario está presente, significa que el usuario debe hacer algún trabajo adicional para inicializar los miembros, por lo que la inicialización de la llave 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 al llamado vtable de la clase, que se establece en el constructor, por lo que la inicialización de llaves sería insuficiente. Podrías descifrar el resto de las restricciones de manera similar a un ejercicio :).

Así que basta de 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 estructura POD es una clase agregada que no tiene miembros de datos no estáticos de tipo estructura no POD, unión no POD (o matriz de dichos tipos) o referencia, y no tiene un operador de asignación de copia definido por el usuario y no 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, unión no-POD (o matriz de dichos tipos) o referencia, y no tiene un operador de asignación de copia definido por el usuario y ningún destructor definido por el usuario. Una clase POD es una clase que es una estructura POD o una unión POD.

Wow, este es más difícil de analizar, ¿no? :) Dejemos de lado las uniones (por los mismos motivos mencionados anteriormente) y reformulemos de una manera un poco más clara:

Una clase agregada se llama POD si no tiene un operador y destructor de asignación de copia definidos 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é POD significa Plain Old Data ?)

  • Todas las clases de POD son agregados o, para decirlo de otra manera, 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-estructura 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<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
};

Las clases POD, las uniones POD, los tipos escalares y las matrices de estos tipos se denominan colectivamente tipos POD.
Los POD son especiales de muchas maneras. Voy a dar algunos ejemplos.

  • Las clases POD son las más cercanas a C structs. 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 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 que todas sus funciones exportadas tomen y devuelvan solo los parámetros de los tipos POD.

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

  • Para los objetos de los tipos de POD, el estándar garantiza que cuando memcpy el contenido de su objeto en una matriz de caracteres char o unsigned char, y luego lo memcpy nuevo en su objeto, el objeto mantendrá su valor original. Tenga en cuenta que no existe tal garantía para los objetos de tipos que no son POD. Además, puede copiar de forma segura los objetos POD 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
    
  • goto declaración. Como usted puede saber, es ilegal (el compilador debería emitir un error) realizar un salto a través de goto desde un punto donde alguna variable aún no estaba en el alcance hasta un punto en el que ya está en el alcance. 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 compilador 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 una clase POD A es del tipo T, puede reinterpret_cast forma segura la reinterpret_cast 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 ves, se comportan de manera diferente para ellos.




Cambios en C ++ 17

Descargue el borrador final de la norma internacional C ++ 17 here .

Agregados

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

Un agregado es una matriz o una clase con
, sin constructores proporcionados, explícitos o heredados por el usuario,
- sin miembros de datos no estáticos privados o protegidos,
- sin funciones virtuales, y
- no hay clases básicas virtuales, privadas o protegidas.
[Nota: la inicialización agregada no permite acceder a miembros o constructores de clases base protegidas y privadas. —Endre nota]
Los elementos de un agregado son:
- para una matriz, los elementos de la matriz en orden de subíndice creciente, o
- para una clase, las clases básicas directas en orden de declaración, seguidas por los miembros de datos no estáticos directos que no son Miembros de un sindicato anónimo, en orden de declaración.

¿Qué cambió?

  1. Los agregados ahora pueden tener clases base públicas, no virtuales. Además, no es un requisito que las clases base sean agregados. Si no son agregados, se inicializan en la lista.
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
    &lt&lt "is C aggregate?: " &lt&lt (std::is_aggregate::value ? 'Y' : 'N')
    &lt&lt " i1: " &lt&lt c.i1 &lt&lt " i2: " &lt&lt c.i2
    &lt&lt " j: " &lt&lt c.j &lt&lt " m.m: " &lt&lt c.m.m &lt&lt endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. Los constructores predeterminados explícitos no están permitidos
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Heredan a los constructores desestimados
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Clases triviales

La definición de clase trivial se volvió a trabajar en C ++ 17 para abordar varios defectos que no se abordaron en C ++ 14. Los cambios fueron de carácter técnico. Aquí está la nueva definición en 12.0.6 (referencias internas eliminadas):

Una clase trivialmente copiable es una clase:
donde cada constructor de copia, constructor de movimiento, operador de asignación de copia y operador de asignación de movimiento es eliminado o trivial,
que tiene al menos un constructor de copia no eliminado, constructor de movimiento, operador de asignación de copia, o mueva el operador de asignación, y
- eso tiene un destructor trivial, no eliminado.
Una clase trivial es una clase que se puede copiar trivialmente y tiene uno o más constructores predeterminados, todos los cuales son triviales o eliminados y al menos uno de ellos no se elimina. [Nota: en particular, una clase trivialmente copiable o trivial no tiene funciones virtuales o clases de base virtual. — nota final]

Cambios:

  1. Bajo C ++ 14, para que una clase fuera trivial, la clase no podía tener ningún operador de construcción / asignación de copia / movimiento que no fuera trivial. Sin embargo, entonces un implícitamente declarado como constructor / operador predeterminado podría ser no trivial y, sin embargo, definido como eliminado porque, por ejemplo, la clase contenía un subobjeto de tipo de clase que no se pudo copiar / mover. La presencia de tal constructor / operador no trivial, definido como eliminado causaría que toda la clase sea no trivial. Un problema similar existía con los destructores. C ++ 17 aclara que la presencia de tales constructores / operadores no hace que la clase sea copiable de forma no trivial, por lo tanto no trivial, y que una clase copiable de forma trivial debe tener un destructor trivial, no eliminado. DR1734 , DR1928
  2. C ++ 14 permitió que una clase trivialmente copiable, por lo tanto una clase trivial, tuviera cada copia / mover constructor / operador de asignación declarado como eliminado. Si tal clase también era un diseño estándar, podría, sin embargo, copiarse / moverse legalmente std::memcpy. Esto fue una contradicción semántica porque, al definir como eliminados a todos los operadores constructores / asignadores, el creador de la clase pretendía claramente que la clase no se pudiera copiar / mover, sin embargo, la clase aún cumplía con la definición de una clase copiable trivialmente. Por lo tanto, en C ++ 17 tenemos una nueva cláusula que establece que la clase trivialmente copiable debe tener al menos un operador trivial, no eliminado (aunque no necesariamente accesible al público) para copiar / mover constructor / asignación. Ver N4148 , DR1734
  3. El tercer cambio técnico se refiere a un problema similar con los constructores por defecto. Bajo C ++ 14, una clase podría tener constructores predeterminados triviales que se definieron implícitamente como eliminados, y aún así ser una clase trivial. La nueva definición aclara que una clase trivial debe tener al menos un constructor predeterminado trivial, no eliminado. Ver DR1496

Clases de diseño estándar

La definición de diseño estándar también fue reelaborada para abordar los informes de defectos. Nuevamente los cambios fueron de naturaleza técnica. Aquí está el texto de la norma (12.0.7). Como antes, se eliminan las referencias internas:

Una clase S es una clase estándar-layout si:
- no tiene miembros de datos no estáticos de la clase de tipo no estándar de diseño (o matriz de tales tipos) o de referencia,
- no tiene funciones virtuales y no hay clases base virtuales,
- tiene el mismo control de acceso para todos los miembros de datos no estáticos,
- no tiene clases base de diseño no estándar,
- tiene como máximo un subobjeto de clase base de cualquier tipo dado,
- tiene todos los miembros de datos no estáticos y campos de bits en la clase y sus clases base declaradas por primera vez en la misma clase, y
- no tiene ningún elemento del conjunto M (S) de tipos (definidos a continuación) como una clase base.108
M (X) se define de la siguiente manera:
- Si X es un tipo de clase sin unión sin miembros de datos no estáticos (posiblemente heredados), el conjunto M (X) está vacío.
- Si X es un tipo de clase sin unión cuyo primer miembro de datos no estáticos tiene el tipo X0 (donde dicho miembro puede ser una unión anónima), el conjunto M (X) consta de X0 y los elementos de M (X0).
- Si X es un tipo de unión, el conjunto M (X) es la unión de todos M (Ui) y el conjunto que contiene toda la Ui, donde cada Ui es el tipo del i miembro de datos no estáticos de X.
- Si X es un tipo de matriz con el tipo de elemento Xe, el conjunto M (X) consta de Xe y los elementos de M (Xe).
- Si X no es una clase, no es un tipo de matriz, el conjunto M (X) está vacío.
[Nota: M (X) es el conjunto de los tipos de todos los subobjetos que no son de la clase base que se garantiza en una clase de diseño estándar para estar en un desplazamiento cero en X. —tendén de notas]
[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
—En ejemplo]
108) Esto garantiza que dos subobjetos que tienen el mismo tipo de clase y que pertenecen al mismo objeto más derivado no se asignan a la misma dirección.

Cambios:

  1. Se aclaró que el requisito de que solo una clase en el árbol de derivación "tiene" miembros de datos no estáticos se refiere a una clase en la que dichos miembros de datos se declaran primero, no a clases donde se pueden heredar, y extendió este requisito a campos de bits no estáticos . También aclaró que una clase de diseño estándar "tiene a lo más un subobjeto de clase base de cualquier tipo dado". Ver DR1813 , DR1881
  2. La definición de diseño estándar nunca ha permitido que el tipo de cualquier clase base sea del mismo tipo que el primer miembro de datos no estáticos. Es para evitar una situación en la que un miembro de datos en el desplazamiento cero tiene el mismo tipo que cualquier clase base. El estándar C ++ 17 proporciona una definición más rigurosa y recursiva de "el conjunto de los tipos de todos los subobjetos de clase no básica que se garantiza en una clase de diseño estándar para estar en un desplazamiento cero" para prohibir dichos tipos De ser el tipo de cualquier clase base. Ver DR1672 , DR2120 .



Hay muchos casos de uso para los punteros.

Comportamiento polimórfico . Para los tipos polimórficos, se utilizan punteros (o referencias) para evitar el corte:

class Base { ... };
class Derived : public Base { ... };

void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }

Derived d;
fun(d);    // oops, all Derived parts silently "sliced" off
gun(&d);   // OK, a Derived object IS-A Base object
hun(d);    // also OK, reference also doesn't slice

Referencia semántica y evitando copiar . Para tipos no polimórficos, un puntero (o una referencia) evitará copiar un objeto potencialmente costoso

Base b;
fun(b);  // copies b, potentially expensive 
gun(&b); // takes a pointer to b, no copying
hun(b);  // regular syntax, behaves as a pointer

Tenga en cuenta que C ++ 11 tiene semánticas de movimiento que pueden evitar muchas copias de objetos caros en el argumento de la función y como valores de retorno. Pero usar un puntero definitivamente los evitará y permitirá múltiples punteros en el mismo objeto (mientras que un objeto solo se puede mover una vez).

Adquisición de recursos . Crear un puntero a un recurso utilizando el new operador es un antipatrón en C ++ moderno. Utilice una clase de recurso especial (uno de los contenedores estándar) o un puntero inteligente ( std::unique_ptr<> o std::shared_ptr<> ). Considerar:

{
    auto b = new Base;
    ...       // oops, if an exception is thrown, destructor not called!
    delete b;
}

contra

{
    auto b = std::make_unique<Base>();
    ...       // OK, now exception safe
}

Un puntero en bruto solo debe usarse como una "vista" y no debe implicar de ninguna manera a la propiedad, ya sea a través de la creación directa o implícitamente a través de los valores de retorno. Consulte también estas preguntas y respuestas de las Preguntas frecuentes de C ++ .

Control de tiempo de vida más detallado Cada vez que se copia un puntero compartido (por ejemplo, como un argumento de función), el recurso al que apunta se mantiene vivo. Los objetos normales (no creados por new , ya sea directamente por usted o dentro de una clase de recurso) se destruyen cuando se salen del alcance.





c++ c++11 aggregate c++17 standard-layout