309 votos

¿Cuáles son los agregados y las vainas y cómo y porqué son especiales?

Esta FAQ es sobre los agregados y las vainas y cubre el siguiente material:

  • ¿Qué son los áridos?
  • ¿ PODs (Plain Old datos)?
  • ¿Cómo se relacionan?
  • ¿Cómo y por qué son especiales?
  • ¿Qué cambia para C ++ 11?

267voto

Lo que cambia para C++11?

Agregados

La definición estándar de un agregado ha cambiado ligeramente, pero sigue siendo prácticamente la misma:

Un agregado es una matriz o una clase (Cláusula 9) sin proporcionados por los usuarios, los constructores (12.1), no brace-o-igual-inicializadores para los no-miembros de datos estáticos (9.2), no private o protected no miembros de datos estáticos (Cláusula 11), no hay clases base (Cláusula 10), y no las funciones virtuales (10.3).

Ok, ¿qué cambió?

  1. Previamente, un agregado no podían tener el usuario declara constructores, pero ahora no puede tener proporcionados por el usuario constructores. Hay una diferencia? Sí, la hay, porque ahora usted puede declarar constructores y en defecto de ellos:

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

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

  2. Ahora un agregado no puede tener ninguna llave-o-igual-inicializadores para los no-miembros de datos estáticos. ¿Qué significa esto? Bueno, esto es debido a que con esta nueva norma, podemos inicializar los miembros directamente en la clase como esta:

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

    Usando esta característica hace que la clase ya no es un agregado, porque es básicamente equivalente a proveer a su propio constructor por defecto.

Así que, ¿qué es un agregado no cambio mucho a todos. Es la misma idea básica, adaptada a las nuevas características.

¿Qué acerca de Vainas?

Vainas pasó por un montón de cambios. Muchas de las reglas anteriores acerca de las Vainas estaban relajados en este nuevo estándar, y la manera en que la definición está previsto en la norma, se cambió radicalmente.

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

  1. Se admite la inicialización estática, y
  2. La compilación de un POD en C++ ofrece el mismo diseño de memoria como una estructura compilado en C.

Debido a esto, la definición se ha dividido en dos conceptos distintos: trivial de clases y estándar-diseño de clases, debido a que estos son más útiles de lo que POD. La norma ahora rara vez se utiliza el término POD, prefiriendo los más específicos trivial y estándar-diseño de conceptos.

La nueva definición básicamente dice que una VAINA es una clase que es a la vez trivial y ha estándar de diseño, y esta propiedad se debe mantener de forma recursiva para todos los que no son miembros de datos estáticos:

Una VAINA estructura es la falta de unión de la clase que es a la vez un trivial de clase y un estándar de clase de diseño, y no tiene ningún no-miembros de datos estáticos de tipo no-POD struct, no POD de la unión (o matriz de tal tipo). Del mismo modo, un POD de la unión es una unión que es a la vez un trivial de clase y un diseño estándar de la clase, y ha los no-miembros de datos estáticos de tipo no-POD struct, no POD de la unión (o matriz de tal tipo). Una VAINA clase es una clase que es un POD estructura o un POD de la unión.

Veamos cada uno de estos dos propiedades en detalle por separado.

Trivial clases

Trivial es la primera propiedad se ha mencionado anteriormente: trivial clases de apoyo de inicialización estática. Si una clase es trivialmente copiable (un superconjunto de trivial clases), es aceptar a la copia su representación sobre el lugar con cosas como memcpy y esperar que el resultado sea el mismo.

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

Un trivialmente copiable clase es una clase que:

- no tiene no trivial de la copia de constructores (12.8),

- no tiene no trivial mover los constructores (12.8),

- no tiene no trivial de la copia de operadores de asignación (13.5.3, 12.8),

- no tiene no trivial mover los operadores de asignación (13.5.3, 12.8), y

- tiene un trivial destructor (12.4).

Un trivial de la clase es una clase que tiene un trivial constructor por defecto (12.1) y es trivialmente copiable.

[ Nota: En particular, un trivialmente copiable o trivial clase no tiene funciones virtuales o virtual de clases base.-nota final ]

Así que, ¿qué son todas esas triviales y no triviales cosas?

Un copiar/mover constructor de la clase X es trivial si no es la proporcionada por el usuario, y si

- clase X no tiene funciones virtuales (10.3) y no virtual de clases base (10.1), y

- el constructor seleccionado para copiar/mover cada clase base directa subobjeto es trivial, y

- para cada uno de los miembro de datos estático de X que es del tipo de clase (o matriz de la misma), el constructor seleccionados para copiar/mover ese miembro es trivial;

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

Básicamente, esto significa que una copia o mover constructor es trivial si no es la proporcionada por el usuario, la clase no tiene nada virtual en ella, y esta propiedad se mantiene de forma recursiva para todos los miembros de la clase y de la clase base.

La definición de un simple copiar/mover operador de asignación es muy similar, simplemente reemplazando la palabra "constructor" con "operador de asignación".

Un trivial destructor también tiene una definición similar, con el añadido de la restricción de que no pueden ser virtuales.

Y, sin embargo, otro de similar existe la regla de trivial defecto de constructores, además de que un constructor por defecto es no trivial si la clase tiene un no-miembros de datos estáticos con llave-o-igual-inicializadores, que hemos visto anteriormente.

Aquí están algunos ejemplos para borrar 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 : Trivial 3 {
    virtual 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
};

Estándar-diseño

Estándar de diseño es la segunda propiedad. La norma menciona que estos son útiles para la comunicación con otros idiomas, y que se debe a un estándar de clase de diseño tiene el mismo diseño de memoria de el equivalente a C struct o union.

Esta es otra de las propiedades que debe poseer de forma recursiva para los miembros y de todas las clases base. Y como de costumbre, no hay funciones virtuales o virtual de la clase base están permitidos. Que iba a hacer el diseño incompatible con C.

Un ambiente relajado regla aquí es que la norma de diseño de las clases deben tener todos los que no son miembros de datos estáticos con el mismo control de acceso. Anteriormente, estos tenían que ser todos los públicos, pero ahora se puede hacer de ellas privadas o protegidas, como son todas privadas o todos protegidos.

Cuando el uso de la herencia, sólo una clase en todo el árbol de herencia puede tener la no-miembros de datos estáticos, y el primer no-miembro de datos estático no puede ser de un tipo de clase base (esto podría romper aliasing reglas), de lo contrario, no es un estándar de clase de diseño.

Esta es la forma en la definición va en el texto estándar:

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

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

- no tiene funciones virtuales (10.3) y no virtual de clases base (10.1),

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

- no es no-estándar-diseño de base de las clases,

- no tiene no miembros de datos estáticos en la mayoría de los derivados de la clase y en la mayoría de una clase base con no miembros de datos estáticos, o que no tiene ninguna base de clases con los no-miembros de datos estáticos, y

- no tiene clases base del mismo tipo como el primer país no miembro de datos estático.

Un estándar de diseño de la estructura es un estándar de clase de diseño se define con la clase-clave struct o la clase-clase de la clave.

Un estándar de diseño de la unión es un estándar de clase de diseño se define con la clase-clave de la unión.

[ Nota: Estándar-diseño de clases son útiles para la comunicación con el código escrito en otros lenguajes de programación. Su diseño se especifica en 9.2.-nota final ]

Y vamos a ver un par de 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 mucho más tipos de Vainas de ahora. E incluso si un tipo que no se POD, podemos tomar ventaja de algunos de los POD de propiedades por separado (si es que sólo uno de trivial o la norma de diseño).

La biblioteca estándar tiene las características 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;

27voto

Nicol Bolas Puntos 133791

por favor, puedes elaborar las reglas siguientes:

Voy a intentar:

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

Eso es simple: todos los no-miembros de datos estáticos deben todos ser public, privateo protected. Usted no puede tener algunos public y algunos private.

El razonamiento de ellos va para el razonamiento para tener una distinción entre "disposición" y "no estándar de diseño". Es decir, para dar el compilador de la libertad para elegir cómo poner las cosas en la memoria. No sólo se trata de vtable punteros.

Cuando se estandarizada de C++ en el 98, que tuvo que, básicamente, predecir cómo la gente podría aplicar. A pesar de tener un poco de experiencia en la implementación con varios sabores de C++, que no estaban seguros acerca de las cosas. Así que decidió ser cauteloso: dar a los compiladores de tanta libertad como sea posible.

Es por eso que la definición de la VAINA en C++98 es tan estricta. Se dio a los compiladores de C++ gran flexibilidad en la distribución de miembro para la mayoría de las clases. Básicamente, POD tipos estaban destinados a ser casos especiales, algo que se escribió específicamente por una razón.

Cuando C++11 se está trabajando, que tenían mucha más experiencia con los compiladores. Y se dieron cuenta de que... el compilador de C++ escritores son muy flojo. Todos tenían esa libertad, pero no hacer nada con ella.

Las reglas de diseño estándar son más o menos la codificación de la práctica común: la mayoría de los compiladores en realidad no tienen que cambiar mucho si nada en absoluto para su aplicación (fuera de quizás algunas cosas para el correspondiente tipo de rasgos).

Ahora, cuando llegó public/private, las cosas son diferentes. La libertad para reordenar la que los miembros son public vs. private realmente importa para el compilador, particularmente en la depuración se basa. Y desde el punto de layout estándar es que no hay compatibilidad con otros idiomas, usted no puede tener el diseño diferente en la depuración del frente de liberación.

Luego está el hecho de que realmente no lastima el usuario. Si estás haciendo un encapsulado de clase, las probabilidades son buenas que todos sus datos serán socios private de todos modos. Por lo general no exponer a los miembros de datos públicos en totalmente encapsulado tipos. Así que esto sería sólo un problema para los pocos usuarios que no quieren hacer eso, que quieren que la división.

Así que no es una gran pérdida.

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

La razón de esto uno vuelve a por qué estandarizados de diseño estándar, de nuevo: la práctica común.

No hay ninguna práctica común cuando se trata de tener a dos miembros de un árbol de herencia, que en realidad almacenar cosas. Algunos ponen la base de la clase antes de la derivada, otros lo hacen de otra manera. Qué manera de ordenar los miembros en caso de que provienen de dos clases base? Y así sucesivamente. Compiladores difieren en gran medida de estas preguntas.

También, gracias a la cero/uno/infinity regla, una vez que usted diga puede tener dos clases con los miembros, se puede decir, como muchos como usted desea. Esto requiere la adición de una gran cantidad de diseño de las reglas para el manejo de este. Usted tiene que decir cómo la herencia múltiple obras, de las clases que poner sus datos antes de otras clases, etc. Eso es un montón de reglas, por muy poco la ganancia material.

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

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

Realmente no puedo hablar de esto. Yo no soy lo suficientemente educadas en C++aliasing reglas para realmente entender esto. Pero tiene algo que ver con el hecho de que la base de miembros compartan la misma dirección de la propia clase base. Que es:

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

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

Y eso es probablemente en contra de C++'s aliasing reglas. De alguna forma.

Sin embargo, ten en cuenta esto: de lo útil que podría tener la capacidad para hacer esto realidad ? Puesto que sólo una clase puede tener la no-miembros de datos estáticos, a continuación, Derived debe ser de esa clase (ya que tiene un Base como un miembro). Así, Base debe estar vacío (de datos). Y si Base está vacío, así como una base de clase... ¿por qué tener un miembro de datos de todo?

Desde Base está vacía, no tiene estado. Por lo que cualquier miembro no estática funciones va a hacer lo que hacen basándose en sus parámetros, no su this de puntero.

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

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X