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



2 Answers

¿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++ c++11 aggregate c++17 standard-layout

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.




Related