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


548

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

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


¿Se puede decir que la motivación detrás de estas definiciones es más o menos: POD == memcpy'able, Aggregate == agregado-inicializable?
Ofek Shilon

Respuestas:


572

Cómo leer:

Este artículo es bastante largo. Si desea saber acerca de los agregados y los POD (datos antiguos simples), tómese el tiempo y léalo. Si solo le interesan 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 saltar 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, incluyendo gramática, estilística, formato, sintaxis, etc.), deje un comentario, lo editaré.

Esta respuesta se aplica a C ++ 03. Para otros estándares de C ++ ver:

¿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 sindicatos, ¿no pueden ser agregados? Sí pueden. En C ++, el término se classrefiere a todas las clases, estructuras y uniones. Entonces, una clase (o estructura, o unión) es un agregado si y solo si cumple 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 hay miembros de datos privados o protegidos no estáticos . Puede tener tantas funciones miembro privadas y protegidas (pero no constructores), así como tantos miembros privados o protegidos de datos estáticos y funciones miembro como 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 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
};

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

Type array_name[n] = {a1, a2, …, am};

if (m == n)
el i- ésimo elemento de la matriz se inicializa con un i
else if (m <n)
los primeros m elementos de la matriz se inicializan con 1 , a 2 , ..., a my los otrosn - melementos son, si es posible, inicializados en valor (vea a continuación la explicación del término) de lo
contrario si (m> n)
el compilador emitirá un error
más (este es el caso cuando n no se especifica del todo int a[] = {1, 2, 3};)
el tamaño de se supone que la matriz (n) es igual a m, por lo queint a[] = {1, 2, 3};es equivalente aint a[3] = {1, 2, 3};

Cuando un objeto de tipo escalar ( bool, int, char, double, punteros, etc.) es de valor inicializado que significa que se inicializa con 0para ese tipo ( falsepara bool, 0.0para double, etc.). Cuando un objeto de tipo de clase con un constructor predeterminado declarado por el usuario tiene un valor inicializado, se llama a su constructor predeterminado. Si el constructor predeterminado se define 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 la idea básica. Una referencia no puede ser inicializada en valor. 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 de la matriz, inicializaremos los miembros de datos no estáticos en el orden de su aparición en la definición de clase (todos son públicos por definición). Si hay menos inicializadores que miembros, el resto tiene valor inicializado. Si es imposible inicializar con 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 y.cse inicializa con 'a', y.x.i1con 10, y.x.i2con 20, y.i[0]con 20, y.i[1]con 30y y.fse inicializa el valor, es decir, se inicializa con 0.0. El miembro estático protegido dno se inicializa en absoluto, porque lo es static.

Las uniones agregadas son diferentes en que puede inicializar solo su primer miembro con llaves. Creo que si está lo suficientemente avanzado en C ++ como para considerar el uso de uniones (su uso puede ser muy peligroso y debe pensarse cuidadosamente), puede buscar las reglas para las uniones en el estándar usted mismo :).

Ahora que sabemos qué tienen de especial los agregados, intentemos comprender las restricciones sobre las clases; Por eso están ahí. Debemos entender que la inicialización de los miembros con llaves implica que la clase no es más que la suma de sus miembros. Si está presente un constructor definido por el usuario, significa que el usuario necesita hacer un trabajo adicional para inicializar los miembros, por lo tanto, la inicialización de llaves 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 sería insuficiente. Podrías resolver el resto de las restricciones de manera similar a un ejercicio :).

Tan suficiente sobre los agregados. Ahora podemos definir un conjunto más estricto de tipos, a saber, POD

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

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

Un POD-struct es una clase agregada que no tiene miembros de datos no estáticos de tipo non-POD-struct, non-POD-union (o array de tales tipos) o referencia, y no tiene operador de asignación de copia definido por el usuario y no destructor definido por el usuario. Del mismo modo, una unión POD es una unión agregada que no tiene miembros de datos no estáticos de tipo non-POD-struct, non-POD-union (o matriz de tales tipos) o referencia, y no tiene operador de asignación de copia definido por el usuario y sin 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 que antes) y reformulemos de una manera más clara:

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

  • Todas las clases de POD son agregados, o, para decirlo al revés, si una clase no es un agregado, entonces seguramente no es un POD
  • Las clases, al igual que las estructuras, pueden ser POD aunque el término estándar sea estructura POD 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 tales tipos se denominan colectivamente tipos POD.
Los POD son especiales en muchos sentidos. Proporcionaré solo algunos ejemplos.

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

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

  • Para los objetos de tipo POD, el estándar garantiza que cuando memcpyel contenido de su objeto se convierta en una matriz de caracteres char o unsigned char, y luego memcpyel contenido vuelva a su objeto, el objeto mantendrá su valor original. Tenga en cuenta que no existe tal garantía para objetos de tipos que no sean POD. Además, puede copiar objetos POD de forma segura con memcpy. El siguiente ejemplo supone que T es un tipo POD:

    #define N sizeof(T)
    char buf[N];
    T obj; // obj initialized to its original value
    memcpy(buf, &obj, N); // between these two calls to memcpy,
    // obj might be modified
    memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
    // holds its original value
  • Ir a la declaración. Como sabrán, es ilegal (el compilador debe emitir un error) realizar un salto a través de goto desde un punto donde alguna variable aún no estaba dentro del alcance hasta un punto donde ya está dentro del 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 de POD A es de tipo T, que puede de manera segura reinterpret_casta partir A*de T*y obtener el puntero al primer elemento y viceversa.

La lista sigue y sigue…

Conclusión

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


3
Buena respuesta. Comentarios: "Si el constructor predeterminado se define implícitamente, todos los miembros no estáticos se inicializan de forma recursiva". y "La inicialización del valor para una clase no agregada puede fallar si, por ejemplo, la clase no tiene un constructor predeterminado apropiado". no es correcto: la inicialización del valor de una clase con un constructor predeterminado declarado implícitamente no necesita un constructor predeterminado definido implícitamente. Por lo tanto, dado (insertar private:según corresponda): struct A { int const a; };entonces A()está bien formado, incluso si Ala definición de constructor por defecto estaría mal formada.
Johannes Schaub - litb

44
@Kev: Si logras empaquetar la misma información en una respuesta más corta, ¡todos lo votaríamos felizmente!
sbi

3
@Armen también tiene en cuenta que puede hacer múltiples respuestas a la misma pregunta. Cada respuesta podría contener parte de la solución a la pregunta. Atornille esa cosa de marca aceptada, en mi opinión :)
Johannes Schaub - litb

3
La respuesta es genial. Todavía vuelvo a visitar esta publicación algunas veces. Por cierto sobre advertencias para Visual Studio. La "declaración de goto" para el pod viene con ignorancia al compilador de MSVC como usted ha mencionado. Pero para la declaración de cambio / caso genera un error de compilación. Basándome en su concepto, hice un test-pod-checker: stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/…
bruziuz

2
En la viñeta que comienza con "La vida útil de los objetos de tipo de clase que no es POD comienza cuando el constructor ha terminado y termina cuando el destructor ha terminado". la última parte debería decir "cuando se inicia el destructor".
Quokka

458

¿Qué cambia para C ++ 11?

Agregados

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

Un agregado es una matriz o una clase (Cláusula 9) sin constructores proporcionados por el usuario (12.1), sin inicializadores de llaves o iguales para miembros de datos no estáticos (9.2), 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).

Ok, que ha cambiado?

  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 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 miembro especial) que está predeterminado 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 POD?

Los POD pasaron por muchos cambios. Muchas de las reglas anteriores sobre los 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. Compilar un POD en C ++ le ofrece 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 raramente usa el término POD, prefiriendo los conceptos triviales y de diseño estándar más específicos .

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 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). Del mismo modo, 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 memcpyy 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 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 trivial predeterminado (12.1) y es trivialmente copiable.

[ Nota: en particular, una clase trivialmente copiable o trivial no tiene funciones virtuales o clases base virtuales. —Final 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 de clase base directa es trivial, y

- para cada miembro de datos no estático de X que es de 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 es proporcionado por el usuario, la clase no tiene nada virtual y esta propiedad es recursiva 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" con "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 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 llaves 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 estos son útiles para comunicarse con otros lenguajes, y eso se debe a que una clase de diseño estándar tiene el mismo diseño de memoria que 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 de costumbre, 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 éstos tuvieron que ser todo público , pero ahora puede que sean privadas o protegidas, siempre y cuando son todos privados o todo protegido.

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 alias), 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,

- o 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ático.

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ódigo escrito en otros lenguajes de programación. Su diseño se especifica en 9.2. —Final 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 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;

2
¿puede por favor elaborar las siguientes reglas: a) las clases de diseño estándar deben tener todos los miembros de datos no estáticos con el mismo control de acceso; b) 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 alias). ¿Especialmente cuáles son las razones para ellos? Para la regla posterior, ¿puede proporcionar un ejemplo de ruptura de alias?
Andriy Tylychko

@AndyT: Mira mi respuesta. Traté de responder a lo mejor de mi conocimiento.
Nicol Bolas

55
Es posible que desee actualizar esto para C ++ 14, que eliminó el requisito "sin llaves ni inicializadores iguales" para los agregados.
TC

@TC gracias por el aviso. Buscaré esos cambios pronto y lo actualizaré.
R. Martinho Fernandes

1
Con respecto al alias: hay una regla de diseño de C ++ que si una clase C tiene una base X (vacía), y el primer miembro de datos de C es tipo X, entonces ese primer miembro no puede estar en el mismo desplazamiento que la base X; obtiene un byte de relleno falso delante de él, si es necesario para evitarlo. Tener dos instancias de X (o subclase) en la misma dirección podría romper cosas que necesitan distinguir diferentes instancias a través de sus direcciones (una instancia vacía no tiene nada más para distinguirla ...). En cualquier caso, la necesidad de poner ese byte de relleno rompe el "diseño compatible".
Greg

106

Lo que ha cambiado para C ++ 14

Podemos consultar el estándar Draft C ++ 14 como referencia.

Agregados

Esto está cubierto 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 privados o protegidos no estáticos (Cláusula 11), sin clases base (Cláusula 10) y sin funciones virtuales (10.3 )

El único cambio ahora es agregar inicializadores de miembros en clase no hace que una clase sea no agregada. Entonces, el siguiente ejemplo de la inicialización agregada de C ++ 11 para clases con inicializadores en ritmo de miembros :

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

no era un agregado en C ++ 11 pero está en C ++ 14. Este cambio está cubierto 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 por Smith que elimina la restricción de que los agregados no pueden tener inicializadores de miembros.

POD permanece igual

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

Un POD struct 110 es una clase no sindical 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 no POD struct, no POD union (o matriz de tales tipos). Del mismo modo, una unión POD es una unión 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). 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.

Cambios de diseño estándar para C ++ 14

Como se señaló en el pod de comentarios, se basa en la definición de diseño estándar y eso sí cambió para C ++ 14, pero esto fue a través de informes de defectos que se aplicaron a C ++ 14 después del hecho.

Había tres DR:

Entonces , el diseño estándar pasó de este Pre C ++ 14:

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

  • (7.1) no tiene miembros de datos no estáticos de tipo clase de diseño no estándar (o conjunto de tales tipos) o referencia,
  • (7.2) no tiene funciones virtuales ([class.virtual]) ni clases base virtuales ([class.mi]),
  • (7.3) tiene el mismo control de acceso (Cláusula [class.access]) para todos los miembros de datos no estáticos,
  • (7.4) no tiene clases base de diseño no estándar,
  • (7.5) o 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
  • (7.6) no tiene clases base del mismo tipo que el primer miembro de datos no estático.109

Para esto en C ++ 14 :

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

  • (3.1) no tiene miembros de datos no estáticos de tipo clase de diseño no estándar (o conjunto de tales tipos) o referencia,
  • (3.2) no tiene funciones virtuales ni clases base virtuales,
  • (3.3) tiene el mismo control de acceso para todos los miembros de datos no estáticos,
  • (3.4) no tiene clases base de diseño no estándar,
  • (3.5) tiene como máximo un subobjeto de clase base de cualquier tipo dado,
  • (3.6) 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
  • (3.7) no tiene ningún elemento del conjunto M (S) de tipos como una clase base, donde para cualquier tipo X, M (X) se define de la siguiente manera.104 [Nota: M (X) es el conjunto de los tipos de todos los subobjetos de clase no base que pueden estar en un desplazamiento cero en X. - nota final]
    • (3.7.1) 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.
    • (3.7.2) Si X es un tipo de clase sin unión con un miembro de datos no estático del tipo X0 que es de tamaño cero o es el primer miembro de datos no estático de X (donde dicho miembro puede ser una unión anónima ), el conjunto M (X) consta de X0 y los elementos de M (X0).
    • (3.7.3) Si X es un tipo de unión, el conjunto M (X) es la unión de todos los M (Ui) y el conjunto que contiene todos los Ui, donde cada Ui es el tipo del i-ésimo miembro de datos no estático de X .
    • (3.7.4) 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).
    • (3.7.5) Si X es un tipo que no es de clase ni de matriz, el conjunto M (X) está vacío.

44
Hay una propuesta para permitir que los agregados tengan una clase base siempre que sea construible por defecto, ver N4404
Shafik Yaghmour

mientras que POD puede permanecer igual, C ++ 14 StandardLayoutType, que es un requisito para POD, ha cambiado de acuerdo con cppref: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 gracias, no sé cómo me los perdí, intentaré actualizar en los próximos días.
Shafik Yaghmour

Avíseme si puede encontrar un ejemplo que sea POD en C ++ 14 pero no en C ++ 11 :-) He comenzado una lista detallada de ejemplos en: stackoverflow.com/questions/146452/what- are-pod-types-in-c / ...
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 así que lo que sucedió aquí es si miramos la descripción del diseño estándar en C ++ 11 y C ++ 14 coinciden. Estos cambios se aplicaron mediante informes de defectos a C ++ 14 Entonces, cuando escribí esto originalmente, era correcto :-p
Shafik Yaghmour

47

¿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 todo ser public, privateo protected. No puedes tener algo publicy 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". Es decir, dar 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, tuvieron que predecir básicamente 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. Entonces decidieron ser cautelosos: dar a los compiladores tanta libertad como sea 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 compiladores. Y se dieron cuenta de que ... los escritores del compilador de C ++ son realmente vagos. Tenían toda esta libertad, pero no hacen nada con ella.

Las reglas del diseño estándar codifican más o menos la práctica común: la mayoría de los compiladores realmente no tuvieron que cambiar mucho para implementarlos (aparte de quizás algunas cosas para los rasgos de tipo correspondientes).

Ahora, cuando se trataba de public/ private, las cosas son diferentes. La libertad de reordenar qué miembros son publicvs.private realidad puede ser importante para el compilador, particularmente en las versiones de depuración. Y dado que el objetivo del diseño estándar es que hay compatibilidad con otros idiomas, no puede hacer que el diseño sea diferente en depuración frente a lanzamiento.

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

Entonces 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 es por qué estandarizaron nuevamente el diseño estándar: 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 al revés. ¿De qué manera ordena a los miembros si provienen de dos clases base? Y así. Los compiladores divergen mucho en estas preguntas.

Además, gracias a la regla de cero / uno / infinito, una vez que dice que puede tener dos clases con miembros, puede decir tantas como desee. Esto requiere agregar muchas reglas de diseño sobre cómo manejar esto. Debe decir cómo funciona la herencia múltiple, qué clases colocan sus datos antes que otras clases, etc. Esas son muchas reglas, con muy poca ganancia de material.

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

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

Realmente no puedo hablar con este. No estoy lo suficientemente educado en las reglas de alias 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 clase base misma. Es decir:

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

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

Y eso probablemente va en contra de las reglas de alias de C ++. De alguna manera.

Sin embargo, considere esto: lo útil que podría tener la capacidad de hacer esto nunca realmente ser? Como solo una clase puede tener miembros de datos no estáticos, Deriveddebe ser esa clase (ya que tiene un Basecomo miembro). Por Base lo tanto, debe estar vacío (de datos). Y si Baseestá vacío, así como una clase base ... ¿por qué tener un miembro de datos?

Como Baseestá vacío, no tiene estado. Por lo tanto, cualquier función miembro no estática hará lo que hace en función de sus parámetros, no de su thispuntero.

Así que de nuevo: sin grandes pérdidas.


Gracias por la explicación, ayuda mucho. Probablemente a pesar de static_cast<Base*>(&d)y &d.bson del mismo Base*tipo, apuntan a cosas diferentes, rompiendo así la regla de alias. Por favor corrigeme.
Andriy Tylychko

1
y, ¿por qué si solo una clase puede tener miembros de datos no estáticos, entonces Deriveddebe ser esa clase?
Andriy Tylychko

3
@AndyT: para que Derivedel primer miembro sea su clase base, debe tener dos cosas: una clase base y un miembro . Y dado que solo una clase en la jerarquía puede tener miembros (y aún ser un diseño estándar), esto significa que su clase base no puede tener miembros.
Nicol Bolas

3
@AndyT, Sí, tienes razón, IME, sobre la regla de alias. Se requieren dos instancias distintas del mismo tipo para tener direcciones de memoria distintas. (Esto permite el seguimiento de la identidad del objeto con direcciones de memoria). El objeto base y el primer miembro derivado son instancias diferentes, por lo que deben tener direcciones diferentes, lo que obliga a agregar relleno, lo que afecta el diseño de la clase. Si fueran de diferentes tipos, no importaría; Los objetos con diferentes tipos pueden tener la misma dirección (una clase y su primer miembro de datos, por ejemplo).
Adam H. Peterson

46

Cambios en C ++ 17

Descargue el borrador final del estándar internacional C ++ 17 aquí .

Agregados

C ++ 17 expande y mejora los agregados y la inicialización de agregados. La biblioteca estándar ahora también incluye 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 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 acceder a miembros o constructores de clases base protegidas y privadas. —Nota final]
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 base directas en orden de declaración, seguidas de los miembros de datos directos no estáticos 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
    << "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  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. Los constructores heredados no están permitidos
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 modificó en C ++ 17 para abordar varios defectos que no se abordaron en C ++ 14. Los cambios fueron de naturaleza técnica. Aquí está la nueva definición en 12.0.6 (referencias internas eliminadas):

Una clase copiable trivialmente 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, constructor de movimiento, operador de asignación de copia no eliminado, o mover operador de asignación, y
- que 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 los cuales no se elimina. [Nota: en particular, una clase trivialmente copiable o trivial no tiene funciones virtuales o clases base virtuales. Nota final]

Cambios:

  1. En C ++ 14, para que una clase sea trivial, la clase no podría tener ningún operador de construcción / asignación de copia / movimiento que no fuera trivial. Sin embargo, un constructor / operador declarado implícitamente como predeterminado podría no ser trivial y, sin embargo, definirse como eliminado porque, por ejemplo, la clase contenía un subobjeto de tipo de clase que no se podía copiar / mover. La presencia de tal constructor / operador no trivial, definido como borrado causaría que toda la clase no sea 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 no trivialmente copiable, por lo tanto, no trivial, y que una clase trivialmente copiable 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 constructor de copia / movimiento / operador de asignación declarado como eliminado. Si dicha clase también fuera un diseño estándar, sin embargo, podría copiarse / moverse legalmente std::memcpy. Esta fue una contradicción semántica, porque, al definir como eliminados todos los operadores de construcción / asignación, el creador de la clase claramente tenía la intención de que la clase no se pudiera copiar / mover, pero la clase aún cumplía con la definición de una clase trivialmente copiable. Por lo tanto, en C ++ 17 tenemos una nueva cláusula que establece que la clase trivialmente copiable debe tener al menos un constructor / operador de copia / movimiento trivial, no eliminado (aunque no necesariamente accesible públicamente). Ver N4148 , DR1734
  3. El tercer cambio técnico se refiere a un problema similar con los constructores predeterminados. Bajo C ++ 14, una clase podría tener constructores triviales predeterminados que se definieron implícitamente como eliminados, pero 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 se modificó para abordar los informes de defectos. Nuevamente, los cambios fueron de naturaleza técnica. Aquí está el texto del estándar (12.0.7). Como antes, se eluyen las referencias internas:

Una clase S es una clase de diseño estándar si:
- 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 ni 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 - Si X es un tipo de clase sin unión cuyo primer miembro de datos no estático tiene 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).
- no tiene ningún elemento del conjunto M (S) de tipos (definidos a continuación) como una clase base.108 - Si X es un tipo de clase sin unión sin ( posiblemente heredados) miembros de datos no estáticos, el conjunto M (X) está vacío.
M (X) se define de la siguiente manera:


- Si X es un tipo de unión, el conjunto M (X) es la unión de todas las M (Ui) y el conjunto que contiene todas las Ui, donde cada Ui es el tipo del miembro de datos no estático i-ésimo 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). [Nota: M (X) es el conjunto de los tipos de todos los subobjetos que no son de clase base que están garantizados en una clase de diseño estándar para estar en un desplazamiento cero en X. —final nota] [Ejemplo:
- Si X es un tipo que no es de clase ni de matriz, el conjunto M (X) está vacío.


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
—Ejemplo final]
108) Esto asegura 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. Aclaró que el requisito de que solo una clase en el árbol de derivación "tenga" miembros de datos no estáticos se refiere a una clase donde dichos miembros de datos se declaran por primera vez, no a las clases donde pueden heredarse, y extendió este requisito a campos de bits no estáticos . También aclaró que una clase de diseño estándar "tiene como máximo un subobjeto de clase base de cualquier tipo". Ver DR1813 , DR1881
  2. La definición de diseño estándar nunca ha permitido que el tipo de ninguna clase base sea del mismo tipo que el primer miembro de datos no estático. 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 base que están garantizados en una clase de diseño estándar para estar en un desplazamiento cero" para prohibir tales tipos de ser el tipo de cualquier clase base. Ver DR1672 , DR2120 .

Nota: El comité de estándares de C ++ pretendía que los cambios anteriores basados ​​en informes de defectos se aplicaran a C ++ 14, aunque el nuevo lenguaje no está en el estándar publicado de C ++ 14. Está en el estándar C ++ 17.


Tenga en cuenta que acabo de actualizar mi respuesta: los defectos de cambios de diseño estándar tienen un estado CD4, lo que significa que en realidad se aplican a C ++ 14. Por eso mi respuesta no los incluyó porque esto sucedió después de que escribí mi respuesta.
Shafik Yaghmour

Tenga en cuenta que comencé una recompensa por esta pregunta.
Shafik Yaghmour

Gracias @ShafikYaghmour. Revisaré el estado de los informes de defectos y modificaré mi respuesta en consecuencia.
ThomasMcLeod

@ShafikYaghmour, después de una revisión del proceso de C ++ 14 y me parece que, mientras estos DR fueron "aceptados" en la reunión de Rapperswil de junio de 2014, el lenguaje de la reunión de Issaquah de febrero de 2014 fue lo que se convirtió en C ++ 14. Consulte isocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting "de acuerdo con las normas ISO, no aprobamos formalmente ninguna edición del documento de trabajo de C ++". ¿Me estoy perdiendo de algo?
ThomasMcLeod

Tienen el estado 'CD4', lo que significa que deben aplicarse en modo C ++ 14.
Shafik Yaghmour

14

¿Qué cambia en

Siguiendo el resto del tema claro de esta pregunta, el significado y el uso de los agregados continúa cambiando con cada estándar. Hay varios cambios clave en el horizonte.

Tipos con constructores declarados por el usuario P1008

En C ++ 17, este tipo sigue siendo un agregado:

struct X {
    X() = delete;
};

Y, por lo tanto, X{}todavía se compila porque eso es una inicialización agregada, no una invocación de constructor. Ver también: ¿ Cuándo un constructor privado no es un constructor privado?

En C ++ 20, la restricción cambiará de requerir:

sin explicitconstructores heredados o proporcionados por el usuario

a

sin constructores declarados por el usuario o heredados

Esto ha sido adoptado en el borrador de trabajo de C ++ 20 . Ni elX aquí ni el Cen la pregunta vinculada serán agregados en C ++ 20.

Esto también produce un efecto de yoyo con el siguiente ejemplo:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

En C ++ 11/14, noB era un agregado debido a la clase base, por lo que realiza una inicialización de valor que llama a qué llamadas , en un punto donde es accesible. Esto estaba bien formado.B{}B::B()A::A()

En C ++ 17, se Bconvirtió en un agregado porque las clases base estaban permitidas, lo que hizo que B{}la inicialización del agregado. Esto requiere una lista de inicialización de copia Adesde {}, pero desde fuera del contexto de B, donde no es accesible. En C ++ 17, esto está mal formado ( auto x = B();aunque estaría bien).

En C ++ 20 ahora, debido al cambio de regla anterior, Buna vez más deja de ser un agregado (no por la clase base, sino por el constructor predeterminado declarado por el usuario, a pesar de que está predeterminado). Así que volvemos a revisar Bel constructor, y este fragmento se forma bien.

Inicializar agregados de una lista de valores entre paréntesis P960

Un problema común que surge es querer usar emplace()constructores de estilo con agregados:

struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error

Esto no funciona, porque emplaceintentará realizar de manera efectiva la inicialización X(1, 2), que no es válida. La solución típica es agregar un constructor X, pero con esta propuesta (actualmente trabajando en Core), los agregados tendrán sintetizados constructores que hagan lo correcto y se comporten como constructores regulares. El código anterior se compilará tal cual en C ++ 20.

Deducción de argumentos de plantilla de clase (CTAD) para agregados P1021 (específicamente P1816 )

En C ++ 17, esto no compila:

template <typename T>
struct Point {
    T x, y;
};

Point p{1, 2}; // error

Los usuarios tendrían que escribir su propia guía de deducción para todas las plantillas agregadas:

template <typename T> Point(T, T) -> Point<T>;

Pero como esto es, en cierto sentido, "lo más obvio" que hacer, y básicamente es solo un ejemplo, el lenguaje lo hará por usted. Este ejemplo se compilará en C ++ 20 (sin la necesidad de la guía de deducciones proporcionada por el usuario).


Aunque voy a votar, parece un poco temprano agregar esto, no sé de nada importante que cambie esto antes de que C ++ 2x esté listo.
Shafik Yaghmour

@ ShafikYaghmour Sí, probablemente MUY temprano. Pero dado que SD era la fecha límite para las nuevas funciones de lenguaje, estos son los únicos dos en vuelo que conozco: el peor de los casos, ¿acabo de eliminar una de estas secciones más tarde? Acabo de ver la pregunta activa con la recompensa y pensé que era un buen momento para intervenir antes de olvidar.
Barry

Entiendo, he sido tentado un par de veces por casos similares. Siempre me preocupa que algo importante cambie y terminaré teniendo que reescribirlo.
Shafik Yaghmour

@ShafikYaghmour Parece que nada va a cambiar aquí :)
Barry

Espero que esto se actualice ahora, con C ++ 20 ya lanzado
Noone AtAll
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.