Cosa sono gli aggregati e i POD e come / perché sono speciali?


548

Questa FAQ riguarda aggregati e POD e copre il seguente materiale:

  • Cosa sono gli aggregati ?
  • Cosa sono i POD (Plain Old Data)?
  • Come sono collegati?
  • Come e perché sono speciali?
  • Cosa cambia per C ++ 11?


Si può dire che la motivazione dietro queste definizioni è approssimativamente: POD == memcpy'able, Aggregate == aggregate-inizializzabile?
Ofek Shilon,

Risposte:


572

Come leggere:

Questo articolo è piuttosto lungo Se vuoi conoscere sia gli aggregati che i POD (Plain Old Data), prenditi del tempo e leggilo. Se sei interessato solo agli aggregati, leggi solo la prima parte. Se si è interessati solo in POD allora è necessario prima leggere la definizione, implicazioni, ed esempi di aggregati e poi si può passare al POD, ma io consiglierei comunque leggere la prima parte nella sua interezza. La nozione di aggregati è essenziale per la definizione dei POD. Se trovi errori (anche minori, inclusi grammatica, stile, formattazione, sintassi, ecc.), Lascia un commento, lo modificherò.

Questa risposta si applica a C ++ 03. Per altri standard C ++ vedere:

Cosa sono gli aggregati e perché sono speciali

Definizione formale dallo standard C ++ ( C ++ 03 8.5.1 §1 ) :

Un aggregato è un array o una classe (clausola 9) senza costruttori dichiarati dall'utente (12.1), senza membri di dati non statici privati ​​o protetti (clausola 11), senza classi di base (clausola 10) e senza funzioni virtuali (10.3 ).

Quindi, OK, analizziamo questa definizione. Prima di tutto, qualsiasi array è un aggregato. Una classe può anche essere un aggregato se ... aspetta! non si dice nulla su strutture o sindacati, non possono essere aggregati? Si Loro possono. In C ++, il termine si classriferisce a tutte le classi, strutture e sindacati. Quindi, una classe (o struttura o unione) è un aggregato se e solo se soddisfa i criteri delle definizioni precedenti. Cosa implicano questi criteri?

  • Ciò non significa che una classe aggregata non possa avere costruttori, in realtà può avere un costruttore predefinito e / o un costruttore di copie purché siano implicitamente dichiarati dal compilatore e non esplicitamente dall'utente

  • Nessun membro di dati non statici privato o protetto . Puoi avere tutte le funzioni membro private e protette (ma non i costruttori), nonché quanti membri dati statici privati ​​o protetti e funzioni membro desideri e non violare le regole per le classi aggregate

  • Una classe aggregata può avere un operatore e / o un distruttore di copia-assegnazione dichiarati dall'utente / definiti dall'utente

  • Un array è un aggregato anche se è un array di tipo di classe non aggregato.

Ora diamo un'occhiata ad alcuni esempi:

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

Ti viene l'idea. Ora vediamo come gli aggregati sono speciali. A differenza delle classi non aggregate, possono essere inizializzate con parentesi graffe {}. Questa sintassi di inizializzazione è comunemente nota per le matrici e abbiamo appena appreso che si tratta di aggregati. Quindi, cominciamo con loro.

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

if (m == n)
l'i esimo elemento della matrice viene inizializzato con i
else if (m <n)
i primi m elementi dell'array vengono inizializzati con un 1 , un 2 , ..., a m e gli altrin - melementi sono, se possibile, inizializzati dal valore (vedi sotto per la spiegazione del termine)
altrimenti se (m> n)
il compilatore genererà un
altro errore (questo è il caso in cui n non è specificato affatto int a[] = {1, 2, 3};)
la dimensione di si presume che l'array (n) sia uguale a m, quindiint a[] = {1, 2, 3};è equivalente aint a[3] = {1, 2, 3};

Quando un oggetto di tipo scalare ( bool, int, char, double, puntatori, ecc) è valore inizializzato significa viene inizializzato con 0per quel tipo ( falseper bool, 0.0per double, ecc). Quando un oggetto di tipo classe con un costruttore predefinito dichiarato dall'utente viene inizializzato con valore, viene chiamato il suo costruttore predefinito. Se il costruttore predefinito viene definito implicitamente, tutti i membri non statici vengono inizializzati in modo ricorsivo. Questa definizione è imprecisa e un po 'errata, ma dovrebbe darti l'idea di base. Un riferimento non può essere inizializzato dal valore. L'inizializzazione del valore per una classe non aggregata può non riuscire se, ad esempio, la classe non ha un costruttore predefinito appropriato.

Esempi di inizializzazione di array:

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
}

Ora vediamo come le classi aggregate possono essere inizializzate con le parentesi graffe. Praticamente allo stesso modo. Invece degli elementi dell'array, inizializzeremo i membri di dati non statici nell'ordine del loro aspetto nella definizione della classe (sono tutti pubblici per definizione). Se ci sono meno inizializzatori rispetto ai membri, il resto viene inizializzato dal valore. Se è impossibile inizializzare il valore di uno dei membri che non sono stati inizializzati esplicitamente, viene visualizzato un errore di compilazione. Se sono presenti più inizializzatori del necessario, viene visualizzato anche un errore di compilazione.

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

Nell'esempio sopra y.cè inizializzato con 'a', y.x.i1con 10, y.x.i2con 20, y.i[0]con 20, y.i[1]con 30ed y.fè inizializzato valore, ovvero inizializzato con 0.0. Il membro statico protetto dnon è affatto inizializzato, perché lo è static.

I sindacati aggregati sono diversi in quanto è possibile inizializzare solo il loro primo membro con parentesi graffe. Penso che se sei abbastanza avanzato in C ++ per prendere in considerazione l'uso dei sindacati (il loro uso può essere molto pericoloso e deve essere considerato attentamente), potresti cercare tu stesso le regole per i sindacati :).

Ora che sappiamo cosa c'è di speciale negli aggregati, proviamo a capire le restrizioni sulle classi; cioè, perché sono lì. Dovremmo capire che l'inizializzazione a livello di membro con parentesi graffe implica che la classe non è altro che la somma dei suoi membri. Se è presente un costruttore definito dall'utente, significa che l'utente deve fare un lavoro extra per inizializzare i membri, pertanto l'inizializzazione del controvento sarebbe errata. Se sono presenti funzioni virtuali, significa che gli oggetti di questa classe hanno (sulla maggior parte delle implementazioni) un puntatore alla cosiddetta vtable della classe, che è impostata nel costruttore, quindi l'inizializzazione del controvento sarebbe insufficiente. Potresti capire il resto delle restrizioni in modo simile a un esercizio :).

Quindi abbastanza sugli aggregati. Ora possiamo definire un insieme più rigoroso di tipi, vale a dire, POD

Cosa sono i POD e perché sono speciali

Definizione formale dallo standard C ++ ( C ++ 03 9 §4 ) :

Una POD-struct è una classe aggregata che non ha membri di dati non statici di tipo non-POD-struct, non-POD-union (o array di tali tipi) o riferimenti, e non ha un operatore di assegnazione della copia definito dall'utente e nessun distruttore definito dall'utente. Allo stesso modo, un'unione POD è un'unione aggregata che non ha membri di dati non statici di tipo non-POD-struct, non-POD-union (o array di tali tipi) o riferimento e non ha un operatore di assegnazione della copia definito dall'utente e nessun distruttore definito dall'utente. Una classe POD è una classe che è una struttura POD o un'unione POD.

Wow, questo è più difficile da analizzare, no? :) Lasciamo fuori i sindacati (per gli stessi motivi di cui sopra) e riformuliamo in modo un po 'più chiaro:

Una classe aggregata viene chiamata POD se non ha un operatore e distruttore di assegnazione copia definiti dall'utente e nessuno dei suoi membri non statici è una classe non POD, un array di non POD o un riferimento.

Cosa implica questa definizione? (Ho già detto che POD sta per Plain Old Data ?)

  • Tutte le classi POD sono aggregati o, per dirla al contrario, se una classe non è un aggregato, non è sicuro che sia un POD
  • Le classi, proprio come le strutture, possono essere POD anche se il termine standard è POD-struct per entrambi i casi
  • Proprio come nel caso degli aggregati, non importa quali membri statici abbiano la classe

Esempi:

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

Le classi POD, i sindacati POD, i tipi scalari e le matrici di tali tipi sono collettivamente chiamati tipi POD.
I POD sono speciali in molti modi. Fornirò solo alcuni esempi.

  • Le classi POD sono le più vicine alle strutture C. A differenza di loro, i POD possono avere funzioni membro e membri statici arbitrari, ma nessuno di questi due modifica il layout di memoria dell'oggetto. Quindi, se si desidera scrivere una libreria dinamica più o meno portatile che può essere utilizzata da C e persino .NET, si dovrebbe provare a fare in modo che tutte le funzioni esportate prendano e restituiscano solo parametri di tipo POD.

  • La durata degli oggetti di tipo di classe non POD inizia al termine del costruttore e termina al termine del distruttore. Per le classi POD, la durata inizia quando la memoria per l'oggetto è occupata e termina quando la memoria viene rilasciata o riutilizzata.

  • Per gli oggetti di tipo POD è garantito dallo standard che quando memcpyil contenuto dell'oggetto viene inserito in un array di caratteri char o unsigned char, e quindi memcpyil contenuto torna nell'oggetto, l'oggetto mantiene il suo valore originale. Si noti che non esiste tale garanzia per oggetti di tipo non POD. Inoltre, puoi tranquillamente copiare oggetti POD con memcpy. L'esempio seguente presuppone che T sia 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
  • vai alla dichiarazione. Come forse saprai, è illegale (il compilatore dovrebbe generare un errore) fare un salto tramite goto da un punto in cui alcune variabili non erano ancora nell'ambito fino a un punto in cui è già nell'ambito. Questa restrizione si applica solo se la variabile è di tipo non POD. Nel seguente esempio f()è mal formato mentre g()è ben formato. Si noti che il compilatore di Microsoft è troppo liberale con questa regola: emette solo un avviso in entrambi i casi.

    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;
    }
  • È garantito che non ci sarà imbottitura all'inizio di un oggetto POD. In altre parole, se il primo membro di una classe POD A è di tipo T, puoi tranquillamente andare reinterpret_castda e ottenere il puntatore al primo membro e viceversa.A*T*

La lista potrebbe continuare all'infinito…

Conclusione

È importante capire cosa sia esattamente un POD perché molte funzionalità del linguaggio, come vedi, si comportano diversamente per loro.


3
Bella risposta. Commenti: "Se il costruttore predefinito è implicitamente definito, tutti i membri non statici vengono inizializzati in modo ricorsivo." e "L'inizializzazione del valore per una classe non aggregata può fallire se, ad esempio, la classe non ha un costruttore predefinito appropriato." non è corretto: l'inizializzazione del valore di una classe con un costruttore predefinito dichiarato implicitamente non richiede un costruttore predefinito definito implicitamente. Quindi, dato (inserire private:come appropriato): struct A { int const a; };allora A()è ben formato, anche se Ala definizione predefinita del costruttore sarebbe mal formata.
Johannes Schaub - litb,

4
@Kev: se riesci a mettere le stesse informazioni in una risposta più breve, saremmo tutti lieti di votare!
sabato

3
@Armen nota anche che puoi fare più risposte alla stessa domanda. Ogni risposta potrebbe contenere parte della soluzione alla domanda. Fanculo quella cosa del segno accettato, secondo me :)
Johannes Schaub - litb

3
La risposta è fantastica Sto ancora rivisitando questo post per alcune volte. A proposito di avvisi per Visual Studio. "goto statement" per pod arriva con ignoranza al compilatore MSVC come hai menzionato. Ma per l'istruzione switch / case genera un errore di compilazione. Sulla base di esso concetto ho fatto qualche test-pod-checker: stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/...
bruziuz

2
Nel punto elenco che inizia con "La durata degli oggetti di tipo di classe non POD inizia al termine del costruttore e termina al termine del distruttore". l'ultima parte dovrebbe invece dire "quando inizia il distruttore".
Quokka,

457

Cosa cambia per C ++ 11?

aggregati

La definizione standard di un aggregato è leggermente cambiata, ma è ancora praticamente la stessa:

Un aggregato è un array o una classe (clausola 9) senza costruttori forniti dall'utente (12.1), senza inizializzatori di parentesi graffa o uguale per membri di dati non statici (9.2), senza membri di dati non statici privati ​​o protetti ( Clausola 11), nessuna classe di base (clausola 10) e nessuna funzione virtuale (10.3).

Ok, cosa è cambiato?

  1. In precedenza, un aggregato non poteva avere costruttori dichiarati dall'utente , ma ora non può avere costruttori forniti dall'utente . C'è una differenza? Sì, c'è, perché ora puoi dichiarare i costruttori e predefinirli :

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

    Questo è ancora un aggregato perché un costruttore (o qualsiasi funzione di membro speciale) che è predefinito sulla prima dichiarazione non è fornito dall'utente.

  2. Ora un aggregato non può avere inizializzatori di parentesi graffa o uguale per membri di dati non statici. Cosa significa questo? Bene, questo è solo perché con questo nuovo standard, possiamo inizializzare i membri direttamente nella classe in questo modo:

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

    L'uso di questa funzione rende la classe non più un aggregato perché sostanzialmente equivale a fornire il proprio costruttore predefinito.

Quindi, ciò che è un aggregato non è cambiato molto. È sempre la stessa idea di base, adattata alle nuove funzionalità.

E i POD?

I POD hanno subito molti cambiamenti. Molte regole precedenti sui POD sono state ridotte in questo nuovo standard e il modo in cui la definizione è fornita nello standard è stato radicalmente modificato.

L'idea di un POD è di acquisire sostanzialmente due proprietà distinte:

  1. Supporta l'inizializzazione statica e
  2. La compilazione di un POD in C ++ fornisce lo stesso layout di memoria di una struttura compilata in C.

Per questo motivo , la definizione è stata suddivisa in due concetti distinti: classi banali e classi di layout standard , perché sono più utili di POD. Lo standard ora usa raramente il termine POD, preferendo i concetti più banali e di layout standard più specifici .

La nuova definizione dice sostanzialmente che un POD è una classe che è al contempo banale e ha un layout standard e questa proprietà deve essere ricorsiva per tutti i membri di dati non statici:

Una struttura POD è una classe non sindacale che è sia una classe banale che una classe di layout standard e non ha membri di dati non statici di tipo struct non-POD, unione non POD (o array di tali tipi). Allo stesso modo, un'unione POD è un'unione che è sia una classe banale che una classe di layout standard e non ha membri di dati non statici di tipo non-POD struct, unione non-POD (o array di tali tipi). Una classe POD è una classe che è una struttura POD o un'unione POD.

Esaminiamo ciascuna di queste due proprietà in dettaglio separatamente.

Classi banali

Trivial è la prima proprietà di cui sopra: classi banali supportano l'inizializzazione statica. Se una classe è banalmente copiabile (un superset di classi banali), è ok copiare la sua rappresentazione sul posto con cose simili memcpye aspettarsi che il risultato sia lo stesso.

Lo standard definisce una classe banale come segue:

Una classe banalmente copiabile è una classe che:

- non ha costruttori di copie non banali (12.8),

- non ha costruttori di mosse non banali (12.8),

- non ha operatori di assegnazione di copie non banali (13.5.3, 12.8),

- non ha operatori di assegnazione di mosse non banali (13.5.3, 12.8) e

- ha un banale distruttore (12.4).

Una banale classe è una classe che ha un banale costruttore predefinito (12.1) ed è banalmente copiabile.

[ Nota: in particolare, una classe banalmente copiabile o banale non ha funzioni virtuali o classi di base virtuali. —Endola nota ]

Quindi, quali sono tutte quelle cose banali e non banali?

Un costruttore di copia / spostamento per la classe X è banale se non è fornito dall'utente e se

- la classe X non ha funzioni virtuali (10.3) e nessuna classe di base virtuale (10.1) e

- il costruttore selezionato per copiare / spostare ogni oggetto secondario della classe base diretta è banale, e

- per ogni membro di dati non statici di X di tipo di classe (o matrice di esso), il costruttore selezionato per copiare / spostare quel membro è banale;

altrimenti il ​​costruttore copia / sposta non è banale.

Fondamentalmente questo significa che un costruttore di copia o spostamento è banale se non è fornito dall'utente, la classe non ha nulla di virtuale in essa e questa proprietà vale ricorsivamente per tutti i membri della classe e per la classe base.

La definizione di un banale operatore di assegnazione di copie / mosse è molto simile, sostituendo semplicemente la parola "costruttore" con "operatore di assegnazione".

Un banale distruttore ha anche una definizione simile, con il vincolo aggiunto che non può essere virtuale.

E ancora esiste un'altra regola simile per i costruttori di default banali, con l'aggiunta che un costruttore di default non è banale se la classe ha membri di dati non statici con inizializzatori di parentesi graffa o uguale , che abbiamo visto sopra.

Ecco alcuni esempi per chiarire tutto:

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

Standard layout

Il layout standard è la seconda proprietà. Lo standard menziona che questi sono utili per comunicare con altre lingue, e questo perché una classe di layout standard ha lo stesso layout di memoria della struttura o unione C equivalente.

Questa è un'altra proprietà che deve essere ricorsiva per i membri e tutte le classi di base. E come al solito, non sono consentite funzioni virtuali o classi di base virtuali. Ciò renderebbe il layout incompatibile con C.

Una regola semplificata qui è che le classi di layout standard devono avere tutti i membri di dati non statici con lo stesso controllo di accesso. In precedenza questi dovevano essere tutti pubblici , ma ora puoi renderli privati ​​o protetti, purché siano tutti privati ​​o tutti protetti.

Quando si utilizza l'ereditarietà, solo una classe nell'intero albero dell'ereditarietà può avere membri di dati non statici e il primo membro di dati non statici non può essere di un tipo di classe di base (ciò potrebbe violare le regole di aliasing), altrimenti non è uno standard- classe di layout.

Ecco come va la definizione nel testo standard:

Una classe di layout standard è una classe che:

- non ha membri di dati non statici di tipo classe di layout non standard (o array di tali tipi) o riferimenti,

- non ha funzioni virtuali (10.3) e nessuna classe di base virtuale (10.1),

- ha lo stesso controllo di accesso (clausola 11) per tutti i membri di dati non statici,

- non ha classi base di layout non standard,

- non ha membri di dati non statici nella classe più derivata e al massimo una classe di base con membri di dati non statici oppure non ha classi di base con membri di dati non statici, e

- non ha classi base dello stesso tipo del primo membro dati non statico.

Una struttura di layout standard è una classe di layout standard definita con la struttura chiave di classe o la classe chiave di classe.

Un'unione di layout standard è una classe di layout standard definita con l'unione chiave di classe.

[ Nota: le classi di layout standard sono utili per comunicare con codice scritto in altri linguaggi di programmazione. Il loro layout è specificato in 9.2. —Endola nota ]

E vediamo alcuni esempi.

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

Conclusione

Con queste nuove regole molti più tipi possono essere POD ora. E anche se un tipo non è POD, possiamo trarre vantaggio da alcune delle proprietà POD separatamente (se si tratta solo di un layout banale o standard).

La libreria standard ha tratti per testare queste proprietà nell'intestazione <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
puoi per favore elaborare le seguenti regole: a) le classi di layout standard devono avere tutti i membri di dati non statici con lo stesso controllo di accesso; b) solo una classe nell'intero albero di ereditarietà può avere membri di dati non statici e il primo membro di dati non statici non può essere di un tipo di classe di base (ciò potrebbe violare le regole di aliasing). Soprattutto quali sono i motivi per loro? Per la regola successiva, puoi fornire un esempio di interruzione dell'aliasing?
Andriy Tylychko,

@AndyT: vedi la mia risposta. Ho cercato di rispondere al meglio delle mie conoscenze.
Nicol Bolas,

5
Potrebbe voler aggiornare questo per C ++ 14, che ha rimosso il requisito "no parentesi graffa o uguale" per gli aggregati.
TC,

@TC grazie per l'heads-up. Cercherò presto quelle modifiche e lo aggiornerò.
R. Martinho Fernandes,

1
Per quanto riguarda l'aliasing: esiste una regola di layout C ++ che se una classe C ha una X (vuota) di base e il primo membro di dati di C è di tipo X, quel primo membro non può essere allo stesso offset della X di base; ottiene un byte di riempimento fittizio prima di esso, se necessario per evitarlo. Avere due istanze di X (o sottoclasse) allo stesso indirizzo potrebbe spezzare le cose che devono distinguere istanze diverse tramite i loro indirizzi (un'istanza vuota non ha nient'altro per distinguerla ...). In ogni caso, la necessità di inserire nel byte di riempimento il "layout compatibile" interrompe.
Greggo

106

Cosa è cambiato per C ++ 14

Possiamo fare riferimento allo standard Draft C ++ 14 per riferimento.

aggregati

Questo è trattato nella sezione 8.5.1 Aggregati che ci fornisce la seguente definizione:

Un aggregato è un array o una classe (clausola 9) senza costruttori forniti dall'utente (12.1), senza membri di dati non statici privati ​​o protetti (clausola 11), senza classi di base (clausola 10) e senza funzioni virtuali (10.3 ).

L'unica modifica è ora l'aggiunta di inizializzatori membri in classe non rende una classe non aggregata. Quindi il seguente esempio da dall'inizializzazione aggregata C ++ 11 per le classi con inizializzatori in-pace dei membri :

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

non era un aggregato in C ++ 11 ma è in C ++ 14. Questa modifica è coperta N3605: inizializzatori e aggregati dei membri , che presenta il seguente estratto:

Bjarne Stroustrup e Richard Smith hanno sollevato un problema relativo all'inizializzazione aggregata e agli inizializzatori dei membri che non funzionano insieme. Questo documento propone di risolvere il problema adottando la formulazione proposta da Smith che rimuove una restrizione secondo cui gli aggregati non possono avere inizializzatori di membri.

POD rimane lo stesso

La definizione per la struttura POD ( semplici vecchi dati ) è trattata nella sezione 9 Classi che dice:

Una struttura POD 110 è una classe non sindacale che è sia una classe banale sia una classe di layout standard e non ha membri di dati non statici di tipo struct non-POD, unione non POD (o array di tali tipi). Allo stesso modo, un'unione POD è un'unione che è sia una classe banale che una classe di layout standard e non ha membri di dati non statici di tipo non strutt POD, unione non POD (o array di tali tipi). Una classe POD è una classe che è una struttura POD o un'unione POD.

che è la stessa formulazione di C ++ 11.

Modifiche al layout standard per C ++ 14

Come notato nel pod dei commenti, si basa sulla definizione di layout standard e questo è cambiato per C ++ 14, ma ciò avveniva tramite segnalazioni di difetti che venivano applicati a C ++ 14 dopo il fatto.

C'erano tre DR:

Quindi il layout standard è passato da questo Pre C ++ 14:

Una classe di layout standard è una classe che:

  • (7.1) non ha membri di dati non statici di tipo classe di layout non standard (o array di tali tipi) o riferimenti,
  • (7.2) non ha funzioni virtuali ([class.virtual]) e nessuna classe di base virtuale ([class.mi]),
  • (7.3) ha lo stesso controllo di accesso (clausola [class.access]) per tutti i membri di dati non statici,
  • (7.4) non ha classi base di layout non standard,
  • (7.5) non ha membri di dati non statici nella classe più derivata e al massimo una classe di base con membri di dati non statici oppure non ha classi di base con membri di dati non statici e
  • (7.6) non ha classi base dello stesso tipo del primo membro dati non statico.109

A questo in C ++ 14 :

Una classe S è una classe di layout standard se:

  • (3.1) non ha membri di dati non statici di tipo classe di layout non standard (o array di tali tipi) o riferimenti,
  • (3.2) non ha funzioni virtuali e nessuna classe di base virtuale,
  • (3.3) ha lo stesso controllo di accesso per tutti i membri di dati non statici,
  • (3.4) non ha classi base di layout non standard,
  • (3.5) ha al massimo un oggetto secondario della classe base di un dato tipo,
  • (3.6) ha tutti i membri di dati e campi bit non statici nella classe e le sue classi base dichiarate per la prima volta nella stessa classe, e
  • (3.7) non ha alcun elemento dell'insieme M (S) di tipi come classe base, dove per qualsiasi tipo X, M (X) è definito come segue.104 [Nota: M (X) è l'insieme dei tipi di tutti gli oggetti secondari di classe non base che possono trovarsi ad un offset zero in X. - nota finale]
    • (3.7.1) Se X è un tipo di classe non sindacale senza membri di dati non statici (possibilmente ereditati), l'insieme M (X) è vuoto.
    • (3.7.2) Se X è un tipo di classe non sindacale con un membro di dati non statico di tipo X0 che è di dimensione zero o è il primo membro di dati non statico di X (dove detto membro può essere un'unione anonima ), l'insieme M (X) è costituito da X0 e dagli elementi di M (X0).
    • (3.7.3) Se X è un tipo di unione, l'insieme M (X) è l'unione di tutta M (Ui) e l'insieme contenente tutta l'Ui, dove ogni Ui è il tipo del suo membro di dati non statico di X .
    • (3.7.4) Se X è un tipo di array con tipo di elemento Xe, l'insieme M (X) è costituito da Xe e dagli elementi di M (Xe).
    • (3.7.5) Se X è un tipo non di classe, non di matrice, l'insieme M (X) è vuoto.

4
Esiste una proposta per consentire agli aggregati di avere una classe base fintanto che è di default costruibile vedi N4404
Shafik Yaghmour

mentre POD potrebbe rimanere lo stesso, C ++ 14 StandardLayoutType, che è un requisito per POD, è cambiato in base a cppref: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 grazie, non so come mi sono perso quelli, proverò ad aggiornare nei prossimi due giorni.
Shafik Yaghmour,

Fammi sapere se riesci a trovare un esempio che è POD in C ++ 14 ma non in C ++ 11 :-) Ho iniziato un elenco dettagliato di esempi su: stackoverflow.com/questions/146452/what- are-pod-types-in-c /…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 quindi quello che è successo qui è se guardiamo la descrizione del layout standard in C ++ 11 e C ++ 14 che corrispondono. Queste modifiche sono state applicate tramite segnalazioni di difetti a C ++ 14. Quindi quando ho scritto questo in origine era corretto :-p
Shafik Yaghmour l'

47

puoi per favore elaborare le seguenti regole:

Ci proverò:

a) le classi di layout standard devono avere tutti i membri di dati non statici con lo stesso controllo di accesso

E 'semplice: tutti i membri di dati non statici devono tutte essere public, privateo protected. Non puoi averne publicalcuniprivate .

Il ragionamento per loro va al ragionamento per avere una distinzione tra "layout standard" e "layout non standard". Vale a dire, per dare al compilatore la libertà di scegliere come mettere le cose in memoria. Non si tratta solo di puntatori vtable.

All'epoca in cui standardizzavano il C ++ nel 98, dovevano praticamente prevedere in che modo le persone lo avrebbero implementato. Sebbene abbiano avuto un po 'di esperienza di implementazione con vari tipi di C ++, non erano sicuri delle cose. Così hanno deciso di essere cauti: dare ai compilatori la massima libertà possibile.

Ecco perché la definizione di POD in C ++ 98 è così rigorosa. Ha dato ai compilatori C ++ grande latitudine sul layout dei membri per la maggior parte delle classi. Fondamentalmente, i tipi di POD erano intesi come casi speciali, qualcosa che hai scritto specificamente per un motivo.

Quando si stava lavorando su C ++ 11, avevano molta più esperienza con i compilatori. E hanno capito che ... Gli autori di compilatori C ++ sono davvero pigri. Avevano tutta questa libertà, ma non lo fanno qualsiasi cosa con esso.

Le regole del layout standard sono più o meno codificanti per la pratica comune: la maggior parte dei compilatori non ha dovuto cambiare molto o nulla per implementarli (al di fuori di alcune cose per i tratti di tipo corrispondenti).

Ora, quando si è trattato di public/ private, le cose sono diverse. La libertà di riordinare quali membri sono publicvs.private effettivamente può importare al compilatore, in particolare nelle build di debug. E poiché il punto del layout standard è che esiste compatibilità con altre lingue, non è possibile avere un layout diverso nel debug rispetto al rilascio.

Quindi c'è il fatto che non danneggia davvero l'utente. Se stai creando una classe incapsulata, le probabilità sono buone che tutti i membri dei tuoi dati lo saranno privatecomunque. In genere non esponete membri di dati pubblici su tipi completamente incapsulati. Quindi questo sarebbe solo un problema per quei pochi utenti che vogliono farlo, che vogliono quella divisione.

Quindi non è una grande perdita.

b) solo una classe nell'intero albero ereditario può avere membri di dati non statici,

La ragione di ciò risale al motivo per cui hanno standardizzato nuovamente il layout standard: pratica comune.

Non c'è pratica comune quando si tratta di avere due membri di un albero ereditario che effettivamente memorizza le cose. Alcuni mettono la classe base prima del derivato, altri lo fanno nell'altro modo. In che modo ordini i membri se provengono da due classi base? E così via. I compilatori divergono notevolmente su queste domande.

Inoltre, grazie alla regola zero / one / infinity, una volta che dici che puoi avere due classi con i membri, puoi dirne quante ne vuoi. Ciò richiede l'aggiunta di molte regole di layout per come gestirlo. Devi dire come funziona l'ereditarietà multipla, quali classi mettono i loro dati prima di altre classi, ecc. Sono molte regole, per un guadagno materiale molto ridotto.

Non puoi creare tutto ciò che non ha funzioni virtuali e un layout standard predefinito del costruttore.

e il primo membro di dati non statico non può essere di un tipo di classe base (questo potrebbe violare le regole di aliasing).

Non posso davvero parlare con questo. Non sono abbastanza istruito nelle regole di aliasing di C ++ per capirlo davvero. Ma ha qualcosa a che fare con il fatto che il membro base condividerà lo stesso indirizzo della classe base stessa. Questo è:

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

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

E questo è probabilmente contro le regole di aliasing di C ++. In qualche modo.

Tuttavia, considerare questo: quanto utile potrebbe avere la possibilità di fare questo mai realmente essere? Poiché solo una classe può avere membri di dati non statici, allora Deriveddeve essere quella classe (poiché ha Baseun membro come). Quindi Base deve essere vuoto (di dati). E se Baseè vuoto, così come una classe di base ... perché avere un membro di dati?

Poiché Baseè vuoto, non ha stato. Quindi qualsiasi funzione membro non statica farà ciò che fa in base ai suoi parametri, non al thispuntatore.

Quindi di nuovo: nessuna grande perdita.


Grazie per la spiegazione, aiuta molto. Probabilmente nonostante static_cast<Base*>(&d)e &d.bsiano dello stesso Base*tipo, indicano cose diverse che infrangono la regola di aliasing. Per favore, correggimi.
Andriy Tylychko,

1
e, perché se solo una classe può avere membri di dati non statici, allora Deriveddeve essere quella classe?
Andriy Tylychko,

3
@AndyT: Affinché Derivedil primo membro sia la sua classe base, deve avere due cose: una classe base e un membro . E poiché solo una classe nella gerarchia può avere membri (ed essere comunque layout standard), ciò significa che la sua classe base non può avere membri.
Nicol Bolas,

3
@AndyT, Sì, hai sostanzialmente ragione, IME, sulla regola di aliasing. Sono necessari due istanze distinte dello stesso tipo per avere indirizzi di memoria distinti. (Ciò consente il tracciamento dell'identità dell'oggetto con indirizzi di memoria.) L'oggetto base e il primo membro derivato sono istanze diverse, quindi devono avere indirizzi diversi, il che forza l'aggiunta del riempimento, influenzando il layout della classe. Se fossero di tipi diversi, non importerebbe; gli oggetti con tipi diversi possono avere lo stesso indirizzo (una classe e il suo primo membro di dati, ad esempio).
Adam H. Peterson,

46

Modifiche in C ++ 17

Scarica qui la bozza finale dello standard internazionale C ++ 17 .

aggregati

C ++ 17 espande e migliora gli aggregati e l'inizializzazione degli aggregati. La libreria standard ora include anche una std::is_aggregateclasse tratto tratto. Ecco la definizione formale dalla sezione 11.6.1.1 e 11.6.1.2 (riferimenti interni elisi):

Un aggregato è un array o una classe con
- nessun costruttore fornito dall'utente, esplicito o ereditato,
- nessun membro di dati non statico privato o protetto,
- nessuna funzione virtuale e
- nessuna classe di base virtuale, privata o protetta.
[Nota: l'inizializzazione aggregata non consente l'accesso ai membri o ai costruttori della classe base protetta e privata. —End note]
Gli elementi di un aggregato sono:
- per una matrice, gli elementi della matrice in ordine crescente di pedice, oppure
- per una classe, le classi di base dirette in ordine di dichiarazione, seguite dai membri di dati non statici diretti che non sono membri di un'unione anonima, in ordine di dichiarazione.

Che cosa è cambiato?

  1. Gli aggregati ora possono avere classi di base pubbliche e non virtuali. Inoltre, non è necessario che le classi base siano aggregate. Se non sono aggregati, vengono inizializzati nell'elenco.
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. I costruttori espliciti predefiniti non sono consentiti
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. I costruttori ereditari non sono ammessi
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Classi Trivial

La definizione di banale classe è stata rielaborata in C ++ 17 per risolvere diversi difetti che non sono stati risolti in C ++ 14. I cambiamenti erano di natura tecnica. Ecco la nuova definizione alla 12.0.6 (riferimenti interni elisi):

Una classe banalmente copiabile è una classe:
- in cui ogni costruttore di copie, costruttore di spostamento, operatore di assegnazione di copie e operatore di assegnazione di spostamenti viene eliminato o banale,
- che abbia almeno un costruttore di copie non cancellato, costruttore di spostamento, operatore di assegnazione di copie, o spostare l'operatore di assegnazione e
- che ha un distruttore banale, non cancellato.
Una classe banale è una classe che è banalmente copiabile e ha uno o più costruttori predefiniti, tutti banali o eliminati e almeno uno dei quali non viene eliminato. [Nota: in particolare, una classe banalmente copiabile o banale non ha funzioni virtuali o classi di base virtuali. Nota finale]

I cambiamenti:

  1. In C ++ 14, affinché una classe sia banale, la classe non potrebbe avere operatori di costruzione / assegnazione di copia / spostamento non banali. Tuttavia, quindi un implicitamente dichiarato come costruttore / operatore predefinito potrebbe essere non banale e tuttavia definito come eliminato perché, ad esempio, la classe conteneva un oggetto secondario di tipo di classe che non poteva essere copiato / spostato. La presenza di tale costruttore / operatore non banale, definito come cancellato, farebbe sì che l'intera classe non fosse banale. Un problema simile esisteva con i distruttori. C ++ 17 chiarisce che la presenza di tali costruttori / operatori non fa sì che la classe sia non banalmente copiabile, quindi non banale, e che una classe banalmente copiabile debba avere un banale distruttore non cancellato. DR1734 , DR1928
  2. C ++ 14 ha permesso a una classe banalmente copiabile, quindi a una banale classe, di far cancellare ogni costruttore / assegnatore di copia / spostamento. Se tale classe era anche un layout standard, potrebbe tuttavia essere copiata / spostata legalmente std::memcpy. Questa era una contraddizione semantica, perché, definendo come cancellati tutti gli operatori di costruzione / assegnazione, il creatore della classe intendeva chiaramente che la classe non poteva essere copiata / spostata, eppure la classe incontrava ancora la definizione di una classe banalmente copiabile. Quindi in C ++ 17 abbiamo una nuova clausola che afferma che la classe banalmente copiabile deve avere almeno un banale operatore di costruzione / assegnazione di copia / spostamento non banale (anche se non necessariamente accessibile pubblicamente). Vedi N4148 , DR1734
  3. La terza modifica tecnica riguarda un problema simile con i costruttori predefiniti. In C ++ 14, una classe potrebbe avere banali costruttori predefiniti che sono stati implicitamente definiti come eliminati, eppure essere comunque una banale classe. La nuova definizione chiarisce che una classe banale deve avere almeno un costruttore predefinito banale, non cancellato. Vedi DR1496

Classi di layout standard

Anche la definizione di layout standard è stata rielaborata per indirizzare i rapporti sui difetti. Ancora una volta i cambiamenti erano di natura tecnica. Ecco il testo dello standard (12.0.7). Come prima, i riferimenti interni sono elisi:

Una classe S è una classe di layout standard se:
- non ha membri di dati non statici di tipo classe di layout non standard (o array di tali tipi) o riferimenti,
- non ha funzioni virtuali e nessuna classe di base virtuale,
- ha lo stesso controllo di accesso per tutti i membri di dati non statici,
- non ha classi di base di layout non standard,
- ha al massimo un oggetto secondario di una classe di base di un dato tipo,
- ha tutti i membri di dati non statici e campi di bit in la classe e le sue classi base sono state dichiarate per la prima volta nella stessa classe e
- non ha alcun elemento dell'insieme M (S) di tipi (definito di seguito) come classe base.108 - Se X è un tipo di classe non sindacale con nessun ( possibilmente ereditato) membri di dati non statici, l'insieme M (X) è vuoto.
M (X) è definita come segue:

- Se X è un tipo di classe non sindacale il cui primo membro di dati non statici ha tipo X0 (dove detto membro può essere un'unione anonima), l'insieme M (X) è costituito da X0 e dagli elementi di M (X0).
- Se X è un tipo di unione, l'insieme M (X) è l'unione di tutta M (Ui) e l'insieme contenente tutta l'Ui, dove ogni Ui è il tipo del suo membro di dati non statico di X.
- Se X è un tipo di array con tipo di elemento Xe, l'insieme M (X) è costituito da Xe e gli elementi di M (Xe).
- Se X è un tipo non di classe, non di matrice, l'insieme M (X) è vuoto.
[Nota: M (X) è l'insieme dei tipi di tutti gli oggetti secondari di classe non base che sono garantiti in una classe di layout standard per essere a un offset zero in X. —end nota]
[Esempio:

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
—End esempio]
108) Ciò garantisce che due oggetti secondari che hanno lo stesso tipo di classe e che appartengono allo stesso oggetto più derivato non siano allocati allo stesso indirizzo.

I cambiamenti:

  1. È stato chiarito che il requisito secondo cui solo una classe nella struttura di derivazione "ha" membri di dati non statici si riferisce a una classe in cui tali membri di dati vengono dichiarati per la prima volta, non a classi in cui possono essere ereditati, e ha esteso questo requisito a campi di bit non statici . È stato inoltre chiarito che una classe di layout standard "ha al massimo un oggetto secondario di una classe base di un determinato tipo". Vedi DR1813 , DR1881
  2. La definizione di layout standard non ha mai permesso al tipo di qualsiasi classe base di essere dello stesso tipo del primo membro di dati non statico. È per evitare una situazione in cui un membro di dati a offset zero ha lo stesso tipo di qualsiasi classe base. Lo standard C ++ 17 fornisce una definizione più rigorosa e ricorsiva di "l'insieme dei tipi di tutti gli oggetti secondari di classe non base garantiti in una classe di layout standard con offset zero" in modo da vietare tali tipi dall'essere il tipo di qualsiasi classe base. Vedi DR1672 , DR2120 .

Nota: il comitato per gli standard C ++ intendeva applicare le modifiche di cui sopra basate sui rapporti sui difetti al C ++ 14, sebbene la nuova lingua non sia nello standard C ++ 14 pubblicato. È nello standard C ++ 17.


Nota: ho appena aggiornato la mia risposta, i difetti delle modifiche al layout standard hanno lo stato CD4, il che significa che sono effettivamente applicati a C ++ 14. Questo è il motivo per cui la mia risposta non li ha inclusi b / c questo è successo dopo che ho scritto la mia risposta.
Shafik Yaghmour,

Nota, ho iniziato una taglia su questa domanda.
Shafik Yaghmour,

Grazie @ShafikYaghmour. Esaminerò lo stato dei rapporti sui difetti e modificherò di conseguenza la mia risposta.
ThomasMcLeod,

@ShafikYaghmour, Dopo alcune revisioni del processo C ++ 14 e mi sembra che, mentre questi DR sono stati "accettati" durante l'incontro di Issaquah del febbraio 2014, il meeting di Rapperswil del giugno 2014 è diventato quello che è diventato C ++ 14. Vedi isocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting "in conformità con le norme ISO non abbiamo approvato formalmente alcuna modifica al documento di lavoro C ++". Mi sto perdendo qualcosa?
ThomasMcLeod,

Hanno lo stato "CD4", il che significa che dovrebbero essere applicati in modalità C ++ 14.
Shafik Yaghmour,

14

Cosa cambia in

Seguendo il resto del chiaro tema di questa domanda, il significato e l'uso degli aggregati continua a cambiare con ogni standard. Ci sono diversi cambiamenti chiave all'orizzonte.

Tipi con costruttori dichiarati dall'utente P1008

In C ++ 17, questo tipo è ancora un aggregato:

struct X {
    X() = delete;
};

E quindi, X{}si compila ancora perché si tratta di inizializzazione aggregata, non di una chiamata del costruttore. Vedi anche: Quando un costruttore privato non è un costruttore privato?

In C ++ 20, la limitazione cambierà dal richiedere:

nessun explicitcostruttore fornito dall'utente o ereditato

per

nessun costruttore dichiarato dall'utente o ereditato

Questo è stato adottato nel progetto di lavoro C ++ 20 . Né Xqui né Cnella domanda collegata saranno aggregati in C ++ 20.

Questo crea anche un effetto yo-yo con il seguente esempio:

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

In C ++ 11/14, nonB era un aggregato a causa della classe base, quindi esegue l'inizializzazione del valore che chiama quali chiamate , in un punto in cui è accessibile. Questo è stato ben formato.B{}B::B()A::A()

In C ++ 17, B divenne un aggregato perché erano consentite le classi di base, il che rendeva l' B{}inizializzazione aggregata. Ciò richiede l'inizializzazione dell'elenco copie Ada {}, ma dall'esterno del contesto di B, dove non è accessibile. In C ++ 17, questo è mal formato ( auto x = B();andrebbe bene però).

In C ++ 20 ora, a causa della modifica della regola sopra, Bcessa ancora una volta di essere un aggregato (non a causa della classe base, ma a causa del costruttore predefinito dichiarato dall'utente - anche se è predefinito). Quindi siamo tornati a esaminare Bil costruttore e questo frammento diventa ben formato.

Inizializzazione di aggregati da un elenco di valori tra parentesi P960

Un problema comune che si presenta è voler usare emplace() costruttori in stile con aggregati:

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

Questo non funziona, perché emplacetenterà di eseguire efficacemente l'inizializzazioneX(1, 2) , che non è valida. La soluzione tipica è quella di aggiungere un costruttore X, ma con questa proposta (che sta attualmente lavorando attraverso Core), gli aggregati avranno effettivamente sintetizzati costruttori che fanno la cosa giusta - e si comporteranno come normali costruttori. Il codice sopra verrà compilato così com'è in C ++ 20.

Deduzione dell'argomento del modello di classe ( CTAD ) per aggregati P1021 (in particolare P1816 )

In C ++ 17, questo non viene compilato:

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

Point p{1, 2}; // error

Gli utenti dovrebbero scrivere la propria guida alla detrazione per tutti i modelli aggregati:

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

Ma poiché in un certo senso questa è "la cosa più ovvia" da fare, ed è fondamentalmente solo una piastra, il linguaggio lo farà per te. Questo esempio verrà compilato in C ++ 20 (senza la necessità della guida alla detrazione fornita dall'utente).


Anche se voterò a fondo, mi sembra un po 'presto per aggiungere questo, non so nulla di grave che potrebbe cambiare questo prima che C ++ 2x venga fatto.
Shafik Yaghmour,

@ShafikYaghmour Sì, probabilmente MODO troppo presto. Ma dato che SD era la scadenza per le nuove funzionalità linguistiche, queste sono le uniche due in volo di cui sono a conoscenza - nel peggiore dei casi ho appena cancellato una di queste sezioni in seguito? Ho appena visto la domanda attiva con la generosità e ho pensato che fosse un buon momento per parlare prima di dimenticare.
Barry,

Capisco, sono stato tentato un paio di volte per casi simili. Mi preoccupo sempre che qualcosa di grave cambierà e finirò per riscriverlo.
Shafik Yaghmour,

@ShafikYaghmour Sembra che nulla cambierà qui :)
Barry

Spero che questo venga aggiornato ora, con C ++ 20 già rilasciato
Noone AtTutti il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.