Was sind Aggregate und PODs und wie / warum sind sie besonders?

548

Diese FAQ befasst sich mit Aggregaten und PODs und behandelt das folgende Material:

  • Was sind Aggregate ?
  • Was sind PODs (Plain Old Data)?
  • Wie hängen sie zusammen?
  • Wie und warum sind sie besonders?
  • Was ändert sich für C ++ 11?
Armen Tsirunyan
quelle
1
Was ist POD-Teilmenge: stackoverflow.com/questions/146452/what-are-pod-types-in-c
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功
Kann man sagen, dass die Motivation hinter diesen Definitionen grob ist: POD == memcpy'able, Aggregate == aggregat-initialisierbar?
Ofek Shilon

Antworten:

571

Wie man liest:

Dieser Artikel ist ziemlich lang. Wenn Sie sowohl über Aggregate als auch über PODs (Plain Old Data) Bescheid wissen möchten, nehmen Sie sich Zeit und lesen Sie diese. Wenn Sie nur an Aggregaten interessiert sind, lesen Sie nur den ersten Teil. Wenn Sie nur in PODs interessiert sind , dann müssen Sie zunächst die Definition, Auswirkungen lesen, und Beispiele für Aggregate und dann Sie können zu PODs springen , aber ich würde noch empfehlen , den ersten Teil in seiner Gesamtheit zu lesen. Der Begriff der Aggregate ist für die Definition von PODs von wesentlicher Bedeutung. Wenn Sie Fehler finden (auch kleinere, einschließlich Grammatik, Stilistik, Formatierung, Syntax usw.), hinterlassen Sie bitte einen Kommentar, den ich bearbeiten werde.

Diese Antwort gilt für C ++ 03. Weitere C ++ - Standards finden Sie unter:

Was sind Aggregate und warum sind sie besonders?

Formale Definition aus dem C ++ - Standard ( C ++ 03 8.5.1 §1 ) :

Ein Aggregat ist ein Array oder eine Klasse (Klausel 9) ohne vom Benutzer deklarierte Konstruktoren (12.1), ohne private oder geschützte nicht statische Datenelemente (Klausel 11), ohne Basisklassen (Klausel 10) und ohne virtuelle Funktionen (10.3) ).

Also, OK, lassen Sie uns diese Definition analysieren. Zuallererst ist jedes Array ein Aggregat. Eine Klasse kann auch ein Aggregat sein, wenn… warten! Über Strukturen oder Gewerkschaften wird nichts gesagt, können sie keine Aggregate sein? Ja, sie können. In C ++ classbezieht sich der Begriff auf alle Klassen, Strukturen und Gewerkschaften. Eine Klasse (oder Struktur oder Vereinigung) ist also genau dann ein Aggregat, wenn sie die Kriterien aus den obigen Definitionen erfüllt. Was bedeuten diese Kriterien?

  • Dies bedeutet nicht, dass eine Aggregatklasse keine Konstruktoren haben kann. Tatsächlich kann sie einen Standardkonstruktor und / oder einen Kopierkonstruktor haben, solange sie implizit vom Compiler und nicht explizit vom Benutzer deklariert werden

  • Keine privaten oder geschützten nicht statischen Datenelemente . Sie können so viele private und geschützte Elementfunktionen (aber keine Konstruktoren) sowie so viele private oder geschützte statische Datenelemente und Elementfunktionen haben, wie Sie möchten, und die Regeln für Aggregatklassen nicht verletzen

  • Eine Aggregatklasse kann einen benutzerdefinierten / benutzerdefinierten Kopierzuweisungsoperator und / oder Destruktor haben

  • Ein Array ist ein Aggregat, auch wenn es sich um ein Array vom nicht aggregierten Klassentyp handelt.

Schauen wir uns nun einige Beispiele an:

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

Du hast die Idee. Nun wollen wir sehen, wie speziell Aggregate sind. Sie können im Gegensatz zu nicht aggregierten Klassen mit geschweiften Klammern initialisiert werden {}. Diese Initialisierungssyntax ist allgemein für Arrays bekannt, und wir haben gerade erfahren, dass es sich um Aggregate handelt. Fangen wir also mit ihnen an.

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

wenn (m == n)
das i- te Element des Arrays mit einem i initialisiert wird,
wenn (m <n)
die ersten m Elemente des Arrays mit einer 1 , einer 2 , ..., einem m und den anderenn - mElementeninitialisiertwerden werden, wenn möglich, wertinitialisiert (siehe unten für die Erläuterung des Begriffs),
andernfalls, wenn (m> n)
der Compiler einen Fehler
ausgibt, andernfalls (dies ist der Fall, wenn n überhaupt nicht wie angegeben ist int a[] = {1, 2, 3};)
die Größe von Es wird angenommen, dass das Array (n) gleich m ist, alsoint a[] = {1, 2, 3};äquivalent zuint a[3] = {1, 2, 3};

Wenn ein Objekt von skalaren Typ ( bool, int, char, double, Zeiger, etc.) ist Wert initialisiert es bedeutet , dass es mit initialisiert wird 0für diesen Typen ( falsefür bool, 0.0für double, etc.). Wenn ein Objekt vom Klassentyp mit einem vom Benutzer deklarierten Standardkonstruktor wertinitialisiert wird, wird sein Standardkonstruktor aufgerufen. Wenn der Standardkonstruktor implizit definiert ist, werden alle nicht statischen Elemente rekursiv wertinitialisiert. Diese Definition ist ungenau und etwas falsch, sollte Ihnen aber die Grundidee vermitteln. Eine Referenz kann nicht wertinitialisiert werden. Die Wertinitialisierung für eine nicht aggregierte Klasse kann fehlschlagen, wenn die Klasse beispielsweise keinen geeigneten Standardkonstruktor hat.

Beispiele für die Array-Initialisierung:

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
}

Nun wollen wir sehen, wie Aggregatklassen mit geschweiften Klammern initialisiert werden können. So ziemlich genauso. Anstelle der Array-Elemente werden die nicht statischen Datenelemente in der Reihenfolge ihres Auftretens in der Klassendefinition initialisiert (sie sind alle per Definition öffentlich). Wenn weniger Initialisierer als Mitglieder vorhanden sind, wird der Rest wertinitialisiert. Wenn es unmöglich ist, eines der Mitglieder, die nicht explizit initialisiert wurden, mit einem Wert zu initialisieren, wird ein Fehler bei der Kompilierung angezeigt. Wenn mehr Initialisierer als erforderlich vorhanden sind, wird auch ein Fehler bei der Kompilierung angezeigt.

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

In dem obigen Beispiel y.cwird initialisiert mit 'a', y.x.i1mit 10, y.x.i2mit 20, y.i[0]mit 20, y.i[1]mit 30und y.fist Wert initialisiert, die mit wird, initialisiert 0.0. Das geschützte statische Element dwird überhaupt nicht initialisiert, da dies der Fall ist static.

Aggregierte Gewerkschaften unterscheiden sich darin, dass Sie nur ihr erstes Mitglied mit geschweiften Klammern initialisieren können. Ich denke, wenn Sie in C ++ weit genug fortgeschritten sind, um überhaupt die Verwendung von Gewerkschaften in Betracht zu ziehen (ihre Verwendung kann sehr gefährlich sein und sorgfältig überlegt werden müssen), könnten Sie die Regeln für Gewerkschaften im Standard selbst nachschlagen :).

Nachdem wir nun wissen, was das Besondere an Aggregaten ist, versuchen wir, die Einschränkungen für Klassen zu verstehen. das ist, warum sie dort sind. Wir sollten verstehen, dass die initiale Initialisierung mit geschweiften Klammern impliziert, dass die Klasse nichts anderes als die Summe ihrer Mitglieder ist. Wenn ein benutzerdefinierter Konstruktor vorhanden ist, bedeutet dies, dass der Benutzer zusätzliche Arbeit leisten muss, um die Elemente zu initialisieren. Daher wäre die Klammerinitialisierung falsch. Wenn virtuelle Funktionen vorhanden sind, bedeutet dies, dass die Objekte dieser Klasse (bei den meisten Implementierungen) einen Zeiger auf die sogenannte vtable der Klasse haben, die im Konstruktor festgelegt ist, sodass eine Klammerinitialisierung unzureichend wäre. Sie könnten den Rest der Einschränkungen auf ähnliche Weise wie bei einer Übung herausfinden :).

Also genug über die Aggregate. Jetzt können wir eine strengere Menge von Typen definieren, nämlich PODs

Was sind PODs und warum sind sie etwas Besonderes?

Formale Definition aus dem C ++ - Standard ( C ++ 03 9 §4 ) :

Eine POD-Struktur ist eine Aggregatklasse, die keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Vereinigung (oder Array solcher Typen) oder Referenz enthält und keinen benutzerdefinierten Kopierzuweisungsoperator und keine hat benutzerdefinierter Destruktor. In ähnlicher Weise ist eine POD-Vereinigung eine aggregierte Vereinigung, die keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Vereinigung (oder Array solcher Typen) oder Referenz enthält und keinen benutzerdefinierten Kopierzuweisungsoperator hat und kein benutzerdefinierter Destruktor. Eine POD-Klasse ist eine Klasse, die entweder eine POD-Struktur oder eine POD-Union ist.

Wow, das ist schwieriger zu analysieren, nicht wahr? :) Lassen wir die Gewerkschaften aus (aus den gleichen Gründen wie oben) weg und formulieren sie etwas klarer:

Eine Aggregatklasse wird als POD bezeichnet, wenn sie keinen benutzerdefinierten Operator und Destruktor für die Kopierzuweisung hat und keines ihrer nicht statischen Elemente eine Nicht-POD-Klasse, ein Array von Nicht-POD oder eine Referenz ist.

Was bedeutet diese Definition? (Habe ich erwähnt, dass POD für Plain Old Data steht ?)

  • Alle POD-Klassen sind Aggregate, oder anders ausgedrückt: Wenn eine Klasse kein Aggregat ist, ist sie sicher kein POD
  • Klassen können genau wie Strukturen PODs sein, obwohl der Standardbegriff in beiden Fällen POD-Struktur ist
  • Genau wie bei Aggregaten spielt es keine Rolle, welche statischen Elemente die Klasse hat

Beispiele:

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

POD-Klassen, POD-Vereinigungen, Skalartypen und Arrays solcher Typen werden zusammen als POD-Typen bezeichnet.
PODs sind in vielerlei Hinsicht etwas Besonderes. Ich werde nur einige Beispiele nennen.

  • POD-Klassen sind C-Strukturen am nächsten. Im Gegensatz zu ihnen können PODs Elementfunktionen und beliebige statische Elemente haben, aber keines dieser beiden Elemente ändert das Speicherlayout des Objekts. Wenn Sie also eine mehr oder weniger portable dynamische Bibliothek schreiben möchten, die von C und sogar von .NET aus verwendet werden kann, sollten Sie versuchen, alle exportierten Funktionen dazu zu bringen, nur Parameter von POD-Typen zu übernehmen und zurückzugeben.

  • Die Lebensdauer von Objekten vom Nicht-POD-Klassentyp beginnt mit dem Abschluss des Konstruktors und endet mit dem Abschluss des Destruktors. Bei POD-Klassen beginnt die Lebensdauer, wenn der Speicher für das Objekt belegt ist, und endet, wenn dieser Speicher freigegeben oder wiederverwendet wird.

  • Für Objekte vom Typ POD wird durch den Standard garantiert , dass das Objekt seinen ursprünglichen Wert memcpybehält, wenn Sie den Inhalt Ihres Objekts in ein Array von Zeichen oder Zeichen ohne Vorzeichen und dann memcpyden Inhalt zurück in Ihr Objekt legen. Beachten Sie, dass für Objekte ohne POD-Typ keine solche Garantie besteht. Sie können POD-Objekte auch sicher mit kopieren memcpy. Im folgenden Beispiel wird davon ausgegangen, dass T ein POD-Typ ist:

    #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
  • gehe zu Aussage. Wie Sie vielleicht wissen, ist es illegal (der Compiler sollte einen Fehler ausgeben), einen Sprung über goto von einem Punkt, an dem eine Variable noch nicht im Gültigkeitsbereich war, zu einem Punkt zu machen, an dem sie bereits im Gültigkeitsbereich liegt. Diese Einschränkung gilt nur, wenn die Variable vom Typ Nicht-POD ist. Im folgenden Beispiel f()ist schlecht geformt, während g()es gut geformt ist. Beachten Sie, dass der Compiler von Microsoft mit dieser Regel zu liberal ist. In beiden Fällen wird lediglich eine Warnung ausgegeben.

    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;
    }
  • Es wird garantiert, dass am Anfang eines POD-Objekts keine Auffüllung erfolgt. Mit anderen Worten, wenn das erste Element einer POD-Klasse A vom Typ T ist, können Sie sicher reinterpret_castvon A*zu T*und den Zeiger auf das erste Element erhalten und umgekehrt.

Die Liste geht weiter und weiter…

Fazit

Es ist wichtig zu verstehen, was genau ein POD ist, da sich viele Sprachfunktionen, wie Sie sehen, für sie unterschiedlich verhalten.

Armen Tsirunyan
quelle
3
Gute Antwort. Kommentare: "Wenn der Standardkonstruktor implizit definiert ist, werden alle nicht statischen Elemente rekursiv wertinitialisiert." und "Die Wertinitialisierung für eine nicht aggregierte Klasse kann fehlschlagen, wenn die Klasse beispielsweise keinen geeigneten Standardkonstruktor hat." ist nicht korrekt: Die Wertinitialisierung einer Klasse mit einem implizit deklarierten Standardkonstruktor erfordert keinen implizit definierten Standardkonstruktor. Also gegeben ( private:ggf. einfügen ): struct A { int const a; };dann A()ist wohlgeformt, auch wenn Adie Standardkonstruktordefinition schlecht geformt wäre.
Johannes Schaub - Litb
4
@Kev: Wenn Sie es schaffen, die gleichen Informationen in eine kürzere Antwort zu packen, würden wir alle gerne darüber abstimmen!
sbi
3
@Armen beachten Sie auch, dass Sie mehrere Antworten auf dieselbe Frage geben können. Jede Antwort könnte einen Teil der Lösung der Frage enthalten. Scheiß auf die akzeptierte Sache, meiner Meinung nach :)
Johannes Schaub - litb
3
Die Antwort ist großartig. Ich besuche diesen Beitrag noch einige Male. Übrigens über Warnungen für Visual Studio. "goto statement" für pod kommt mit Unwissenheit zum MSVC-Compiler, wie Sie erwähnt haben. Bei der switch / case-Anweisung wird jedoch ein Kompilierungsfehler generiert. Basierend auf diesem Konzept habe ich einige Test-Pod-Checker gemacht: stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/…
Bruziuz
2
In dem Aufzählungspunkt, der mit "Die Lebensdauer von Objekten vom Nicht-POD-Klassentyp beginnt mit dem Abschluss des Konstruktors und endet mit dem Abschluss des Destruktors" beginnt. Der letzte Teil sollte stattdessen "wann der Destruktor startet" sagen.
Quokka
457

Was ändert sich für C ++ 11?

Aggregate

Die Standarddefinition eines Aggregats hat sich geringfügig geändert, ist aber immer noch ziemlich gleich:

Ein Aggregat ist ein Array oder eine Klasse (Abschnitt 9) ohne vom Benutzer bereitgestellte Konstruktoren (12.1), ohne Klammer- oder Gleichheitsinitialisierer für nicht statische Datenelemente (9.2), ohne private oder geschützte nicht statische Datenelemente (Abschnitt 9). Klausel 11), keine Basisklassen (Klausel 10) und keine virtuellen Funktionen (10.3).

Ok, was hat sich geändert?

  1. Früher konnte ein Aggregat keine vom Benutzer deklarierten Konstruktoren haben, jetzt kann es keine vom Benutzer bereitgestellten Konstruktoren mehr haben. Ist da ein Unterschied? Ja, denn jetzt können Sie Konstruktoren deklarieren und standardmäßig festlegen:

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

    Dies ist immer noch ein Aggregat, da ein Konstruktor (oder eine spezielle Elementfunktion ) , der in der ersten Deklaration standardmäßig verwendet wird, nicht vom Benutzer bereitgestellt wird.

  2. Jetzt kann ein Aggregat keine Klammer- oder Gleichheitsinitialisierer für nicht statische Datenelemente haben. Was bedeutet das? Das liegt nur daran, dass wir mit diesem neuen Standard Mitglieder direkt in der Klasse wie folgt initialisieren können:

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

    Durch die Verwendung dieser Funktion wird die Klasse nicht mehr zu einem Aggregat, da dies im Wesentlichen der Bereitstellung eines eigenen Standardkonstruktors entspricht.

Was also ein Aggregat ist, hat sich überhaupt nicht geändert. Es ist immer noch die gleiche Grundidee, angepasst an die neuen Funktionen.

Was ist mit PODs?

PODs haben viele Änderungen durchlaufen. Viele frühere Regeln zu PODs wurden in diesem neuen Standard gelockert, und die Art und Weise, wie die Definition im Standard bereitgestellt wird, wurde radikal geändert.

Die Idee eines POD besteht darin, grundsätzlich zwei unterschiedliche Eigenschaften zu erfassen:

  1. Es unterstützt die statische Initialisierung und
  2. Wenn Sie einen POD in C ++ kompilieren, erhalten Sie dasselbe Speicherlayout wie eine in C kompilierte Struktur.

Aus diesem Grund wurde die Definition in zwei unterschiedliche Konzepte unterteilt: Trivialklassen und Standardlayoutklassen , da diese nützlicher als POD sind. Der Standard verwendet jetzt selten den Begriff POD und bevorzugt die spezifischeren Trivial- und Standardlayoutkonzepte .

Die neue Definition besagt im Wesentlichen, dass ein POD eine Klasse ist, die sowohl trivial ist als auch ein Standardlayout aufweist. Diese Eigenschaft muss für alle nicht statischen Datenelemente rekursiv gelten:

Eine POD-Struktur ist eine Nicht-Vereinigungsklasse, die sowohl eine Trivialklasse als auch eine Standardlayoutklasse ist und keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Vereinigung (oder Array solcher Typen) enthält. In ähnlicher Weise ist eine POD-Vereinigung eine Vereinigung, die sowohl eine triviale Klasse als auch eine Standardlayoutklasse ist und keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Vereinigung (oder Array solcher Typen) enthält. Eine POD-Klasse ist eine Klasse, die entweder eine POD-Struktur oder eine POD-Union ist.

Lassen Sie uns jede dieser beiden Eigenschaften einzeln im Detail betrachten.

Trivialklassen

Trivial ist die erste oben erwähnte Eigenschaft: Trivialklassen unterstützen die statische Initialisierung. Wenn eine Klasse trivial kopierbar ist (eine Obermenge trivialer Klassen), ist es in Ordnung, ihre Darstellung über den Ort mit Dingen wie zu kopieren memcpyund zu erwarten, dass das Ergebnis dasselbe ist.

Der Standard definiert eine triviale Klasse wie folgt:

Eine trivial kopierbare Klasse ist eine Klasse, die:

- hat keine nicht trivialen Kopierkonstruktoren (12.8),

- hat keine nicht trivialen Verschiebungskonstruktoren (12.8),

- hat keine nicht trivialen Kopierzuweisungsoperatoren (13.5.3, 12.8),

- hat keine nicht trivialen Operatoren für die Bewegungszuweisung (13.5.3, 12.8) und

- hat einen trivialen Destruktor (12.4).

Eine triviale Klasse ist eine Klasse, die einen trivialen Standardkonstruktor (12.1) hat und trivial kopierbar ist.

[ Hinweis: Insbesondere verfügt eine trivial kopierbare oder trivial Klasse nicht über virtuelle Funktionen oder virtuelle Basisklassen. - Endnote ]

Also, was sind all diese trivialen und nicht trivialen Dinge?

Ein Kopier- / Verschiebungskonstruktor für Klasse X ist trivial, wenn er nicht vom Benutzer bereitgestellt wird und wenn

- Klasse X hat keine virtuellen Funktionen (10.3) und keine virtuellen Basisklassen (10.1) und

- Der Konstruktor, der zum Kopieren / Verschieben jedes direkten Basisklassen-Unterobjekts ausgewählt wurde, ist trivial

- Für jedes nicht statische Datenelement von X, das vom Klassentyp (oder einem Array davon) ist, ist der Konstruktor, der zum Kopieren / Verschieben dieses Elements ausgewählt wurde, trivial.

Andernfalls ist der Kopier- / Verschiebungskonstruktor nicht trivial.

Grundsätzlich bedeutet dies, dass ein Kopier- oder Verschiebungskonstruktor trivial ist, wenn er nicht vom Benutzer bereitgestellt wird, die Klasse nichts Virtuelles enthält und diese Eigenschaft rekursiv für alle Mitglieder der Klasse und für die Basisklasse gilt.

Die Definition eines einfachen Kopier- / Verschiebungszuweisungsoperators ist sehr ähnlich und ersetzt einfach das Wort "Konstruktor" durch "Zuweisungsoperator".

Ein trivialer Destruktor hat auch eine ähnliche Definition, mit der zusätzlichen Einschränkung, dass er nicht virtuell sein kann.

Und es gibt noch eine ähnliche Regel für triviale Standardkonstruktoren, mit dem Zusatz, dass ein Standardkonstruktor nicht trivial ist, wenn die Klasse nicht statische Datenelemente mit Klammer- oder Gleichheitsinitialisierern hat , wie wir oben gesehen haben.

Hier sind einige Beispiele, um alles zu klären:

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

Standardlayout

Standard-Layout ist die zweite Eigenschaft. Der Standard erwähnt, dass diese für die Kommunikation mit anderen Sprachen nützlich sind, und das liegt daran, dass eine Standardlayoutklasse das gleiche Speicherlayout wie die entsprechende C-Struktur oder Union hat.

Dies ist eine weitere Eigenschaft, die für Mitglieder und alle Basisklassen rekursiv gelten muss. Und wie üblich sind keine virtuellen Funktionen oder virtuellen Basisklassen erlaubt. Das würde das Layout mit C inkompatibel machen.

Eine entspannte Regel lautet hier, dass Standardlayoutklassen alle nicht statischen Datenelemente mit derselben Zugriffssteuerung haben müssen. Früher mussten diese alle öffentlich sein , jetzt können Sie sie privat oder geschützt machen, solange sie alle privat oder alle geschützt sind.

Bei Verwendung der Vererbung kann nur eine Klasse im gesamten Vererbungsbaum nicht statische Datenelemente haben, und das erste nicht statische Datenelement kann nicht vom Typ einer Basisklasse sein (dies könnte gegen Aliasing-Regeln verstoßen). Andernfalls handelt es sich nicht um einen Standard. Layoutklasse.

So sieht die Definition im Standardtext aus:

Eine Standardlayoutklasse ist eine Klasse, die:

- hat keine nicht statischen Datenelemente vom Typ Nicht-Standard-Layout-Klasse (oder Array solcher Typen) oder Referenz,

- hat keine virtuellen Funktionen (10.3) und keine virtuellen Basisklassen (10.1),

- hat die gleiche Zugriffskontrolle (Abschnitt 11) für alle nicht statischen Datenelemente,

- hat keine Basisklassen ohne Standardlayout,

- hat entweder keine nicht statischen Datenelemente in der am meisten abgeleiteten Klasse und höchstens eine Basisklasse mit nicht statischen Datenelementen oder keine Basisklassen mit nicht statischen Datenelementen und

- hat keine Basisklassen des gleichen Typs wie das erste nicht statische Datenelement.

Eine Standardlayoutstruktur ist eine Standardlayoutklasse, die mit der Klassenschlüsselstruktur oder der Klassenschlüsselklasse definiert wird.

Eine Standard-Layout-Vereinigung ist eine Standard-Layout-Klasse, die mit der Klassenschlüssel-Vereinigung definiert wird.

[ Hinweis: Standardlayoutklassen sind nützlich für die Kommunikation mit Code, der in anderen Programmiersprachen geschrieben wurde. Ihr Layout ist in 9.2 angegeben. - Endnote ]

Und sehen wir uns ein paar Beispiele an.

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

Fazit

Mit diesen neuen Regeln können jetzt viel mehr Typen PODs sein. Und selbst wenn ein Typ kein POD ist, können wir einige der POD-Eigenschaften separat nutzen (wenn es sich nur um ein triviales oder ein Standard-Layout handelt).

Die Standardbibliothek verfügt über Eigenschaften zum Testen dieser Eigenschaften im Header <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;
R. Martinho Fernandes
quelle
2
Können Sie bitte folgende Regeln ausarbeiten: a) Standardlayoutklassen müssen alle nicht statischen Datenelemente mit derselben Zugriffskontrolle haben; b) Nur eine Klasse im gesamten Vererbungsbaum kann nicht statische Datenelemente haben, und das erste nicht statische Datenelement kann nicht vom Typ einer Basisklasse sein (dies könnte gegen Aliasing-Regeln verstoßen). Besonders was sind die Gründe für sie? Können Sie für die spätere Regel ein Beispiel für das Unterbrechen von Aliasing angeben?
Andriy Tylychko
@AndyT: Siehe meine Antwort. Ich habe versucht, nach bestem Wissen zu antworten.
Nicol Bolas
5
Möglicherweise möchten Sie dies für C ++ 14 aktualisieren, wodurch die Anforderung "Keine Klammer oder gleiche Initialisierer" für Aggregate entfernt wurde.
TC
@TC danke für Heads-up. Ich werde diese Änderungen bald nachschlagen und aktualisieren.
R. Martinho Fernandes
1
In Bezug auf das Aliasing: Es gibt eine C ++ - Layoutregel: Wenn eine Klasse C eine (leere) Basis X hat und das erste Datenelement von C vom Typ X ist, kann dieses erste Element nicht den gleichen Versatz wie die Basis X haben. Es wird ein Dummy-Padding-Byte vorangestellt, falls dies erforderlich ist, um dies zu vermeiden. Wenn zwei Instanzen von X (oder Unterklasse) an derselben Adresse vorhanden sind, können Dinge beschädigt werden, die unterschiedliche Instanzen über ihre Adressen unterscheiden müssen (eine leere Instanz hat nichts anderes zu unterscheiden ...). In jedem Fall bricht die Notwendigkeit, dieses Füllbyte einzugeben, "Layout-kompatibel".
Greggo
106

Was hat sich für C ++ 14 geändert?

Wir können uns auf den Draft C ++ 14-Standard als Referenz beziehen.

Aggregate

Dies wird im Abschnitt behandelt 8.5.1 Aggregate behandelt, der uns die folgende Definition gibt:

Ein Aggregat ist ein Array oder eine Klasse (Abschnitt 9) ohne vom Benutzer bereitgestellte Konstruktoren (12.1), ohne private oder geschützte nicht statische Datenelemente (Abschnitt 11), ohne Basisklassen (Abschnitt 10) und ohne virtuelle Funktionen (10.3) ).

Die einzige Änderung besteht nun darin, dass durch das Hinzufügen von Elementinitialisierern in der Klasse eine Klasse nicht zu einem Aggregat wird. Das folgende Beispiel aus C ++ 11 aggregiert die Initialisierung für Klassen mit In-Pace-Initialisierern für Mitglieder :

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

war kein Aggregat in C ++ 11, aber es ist in C ++ 14. Diese Änderung wird in N3605 behandelt: Elementinitialisierer und -aggregate mit der folgenden Zusammenfassung:

Bjarne Stroustrup und Richard Smith haben ein Problem angesprochen, bei dem die Aggregatinitialisierung und die Initialisierung von Mitgliedern nicht zusammenarbeiten. In diesem Dokument wird vorgeschlagen, das Problem zu beheben, indem der von Smith vorgeschlagene Wortlaut übernommen wird, mit dem eine Einschränkung beseitigt wird, dass Aggregate keine Mitgliederinitialisierer haben können.

POD bleibt gleich

Die Definition für die POD- Struktur ( Plain Old Data ) wird im Abschnitt 9 Klassen behandelt, in dem Folgendes steht:

Eine POD-Struktur 110 ist eine Nicht-Vereinigungsklasse, die sowohl eine Trivialklasse als auch eine Standardlayoutklasse ist und keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Vereinigung (oder Array solcher Typen) enthält. In ähnlicher Weise ist eine POD-Vereinigung eine Vereinigung, die sowohl eine Trivialklasse als auch eine Standardlayoutklasse ist und keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Vereinigung (oder Array solcher Typen) enthält. Eine POD-Klasse ist eine Klasse, die entweder eine POD-Struktur oder eine POD-Union ist.

Das ist der gleiche Wortlaut wie in C ++ 11.

Änderungen des Standardlayouts für C ++ 14

Wie in den Kommentaren erwähnt, basiert der Pod auf der Definition des Standardlayouts , die sich für C ++ 14 geändert hat. Dies geschah jedoch über Fehlerberichte, die nachträglich auf C ++ 14 angewendet wurden.

Es gab drei DRs:

Das Standardlayout ging also von diesem Pre C ++ 14 aus:

Eine Standardlayoutklasse ist eine Klasse, die:

  • (7.1) hat keine nicht statischen Datenelemente vom Typ Nicht-Standard-Layout-Klasse (oder Array solcher Typen) oder Referenz,
  • (7.2) hat keine virtuellen Funktionen ([class.virtual]) und keine virtuellen Basisklassen ([class.mi]),
  • (7.3) hat für alle nicht statischen Datenelemente dieselbe Zugriffssteuerung (Klausel [class.access]).
  • (7.4) hat keine Basisklassen ohne Standardlayout.
  • (7.5) hat entweder keine nicht statischen Datenelemente in der am meisten abgeleiteten Klasse und höchstens eine Basisklasse mit nicht statischen Datenelementen oder keine Basisklassen mit nicht statischen Datenelementen und
  • (7.6) hat keine Basisklassen des gleichen Typs wie das erste nicht statische Datenelement.109

Dazu in C ++ 14 :

Eine Klasse S ist eine Standardlayoutklasse, wenn:

  • (3.1) hat keine nicht statischen Datenelemente vom Typ Nicht-Standard-Layout-Klasse (oder Array solcher Typen) oder Referenz,
  • (3.2) hat keine virtuellen Funktionen und keine virtuellen Basisklassen,
  • (3.3) hat die gleiche Zugriffskontrolle für alle nicht statischen Datenelemente.
  • (3.4) hat keine Nicht-Standard-Layout-Basisklassen,
  • (3.5) hat höchstens ein Basisklassen-Unterobjekt eines bestimmten Typs,
  • (3.6) hat alle nicht statischen Datenelemente und Bitfelder in der Klasse und ihre Basisklassen, die zuerst in derselben Klasse deklariert wurden, und
  • (3.7) hat kein Element der Menge M (S) von Typen als Basisklasse, wobei für jeden Typ X M (X) wie folgt definiert ist.104 [Anmerkung: M (X) ist die Menge der Typen von alle Nichtobjekte der Nicht-Basisklasse, die in X möglicherweise einen Nullpunktversatz aufweisen. - Endnote]
    • (3.7.1) Wenn X ein nicht gewerkschaftlich organisierter Klassentyp ohne (möglicherweise geerbte) nicht statische Datenelemente ist, ist die Menge M (X) leer.
    • (3.7.2) Wenn X ein nicht gewerkschaftlicher Klassentyp mit einem nicht statischen Datenelement vom Typ X0 ist, das entweder die Größe Null hat oder das erste nicht statische Datenelement von X ist (wobei dieses Mitglied eine anonyme Vereinigung sein kann ) besteht die Menge M (X) aus X0 und den Elementen von M (X0).
    • (3.7.3) Wenn X ein Vereinigungstyp ist, ist die Menge M (X) die Vereinigung aller M (Ui) und die Menge, die alle Ui enthält, wobei jede Ui der Typ des i-ten nicht statischen Datenelements von X ist .
    • (3.7.4) Wenn X ein Array-Typ mit dem Elementtyp Xe ist, besteht die Menge M (X) aus Xe und den Elementen von M (Xe).
    • (3.7.5) Wenn X ein Nicht-Klassen- oder Nicht-Array-Typ ist, ist die Menge M (X) leer.
Shafik Yaghmour
quelle
4
Es gibt einen Vorschlag, Aggregaten zu erlauben, eine Basisklasse zu haben, solange sie standardmäßig konstruierbar ist, siehe N4404
Shafik Yaghmour
Während POD möglicherweise gleich bleibt, hat sich C ++ 14 StandardLayoutType, eine Voraussetzung für POD, gemäß cppref geändert: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功
1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 Danke, ich weiß nicht, wie ich diese verpasst habe, ich werde versuchen, sie in den nächsten Tagen zu aktualisieren.
Shafik Yaghmour
Lassen Sie mich wissen, ob Sie ein Beispiel mit POD in C ++ 14, aber nicht in C ++ 11 finden können :-) Ich habe eine detaillierte Liste von Beispielen unter stackoverflow.com/questions/146452/what- erstellt. are-pod-types-in-c /…
Ciro Santilli 法轮功 冠状 病 病 六四 事件 25.
1
@CiroSantilli happened 改造 中心 六四 事件 法轮功 Was hier passiert ist, ist, wenn wir uns die Beschreibung des Standardlayouts in C ++ 11 und C ++ 14 ansehen, mit der sie übereinstimmen. Diese Änderungen wurden über Fehlerberichte auf C ++ 14 angewendet. Als ich das ursprünglich schrieb, war es richtig :-p
Shafik Yaghmour
47

Können Sie bitte folgende Regeln ausarbeiten:

Ich werde es versuchen:

a) Standardlayoutklassen müssen alle nicht statischen Datenelemente mit derselben Zugriffssteuerung haben

Das ist einfach: Alle nicht statischen Datenelemente müssen alle sein public, privateoder protected. Sie können einige publicund einige nicht haben private.

Die Begründung für sie geht auf die Begründung zurück, überhaupt zwischen "Standardlayout" und "Nicht-Standardlayout" zu unterscheiden. Um dem Compiler die Freiheit zu geben, zu entscheiden, wie Dinge gespeichert werden sollen. Es geht nicht nur um vtable-Zeiger.

Als sie C ++ im Jahr 98 standardisierten, mussten sie grundsätzlich vorhersagen, wie die Leute es implementieren würden. Obwohl sie einiges an Implementierungserfahrung mit verschiedenen C ++ - Varianten hatten, waren sie sich über die Dinge nicht sicher. Also beschlossen sie, vorsichtig zu sein: Geben Sie den Compilern so viel Freiheit wie möglich.

Deshalb ist die Definition von POD in C ++ 98 so streng. Es gab C ++ - Compilern für die meisten Klassen einen großen Spielraum beim Layout der Mitglieder. Grundsätzlich sollten POD-Typen Sonderfälle sein, was Sie speziell aus einem bestimmten Grund geschrieben haben.

Als an C ++ 11 gearbeitet wurde, hatten sie viel mehr Erfahrung mit Compilern. Und sie erkannten, dass ... C ++ - Compiler-Autoren wirklich faul sind. Sie hatten all diese Freiheit, aber sie taten es nicht haben nichts damit gemacht.

Die Regeln des Standardlayouts kodifizieren mehr oder weniger die gängige Praxis: Die meisten Compiler mussten nicht wirklich viel oder gar nichts ändern, um sie zu implementieren (abgesehen von einigen Dingen für die entsprechenden Typmerkmale).

Wenn es um public/ geht private, sind die Dinge anders. Die Freiheit, neu zu ordnen, welche Mitglieder sindpublic vs.private tatsächlich an den Compiler Rolle, vor allem bei der Fehlersuche aufbaut. Und da der Punkt des Standardlayouts darin besteht, dass es Kompatibilität mit anderen Sprachen gibt, kann das Layout in Debug und Release nicht anders sein.

Dann gibt es die Tatsache, dass es den Benutzer nicht wirklich verletzt. Wenn Sie eine gekapselte Klasse erstellen, stehen die Chancen gut, dass alle Ihre Datenmitglieder es privatetrotzdem sind. Im Allgemeinen machen Sie öffentliche Datenelemente nicht für vollständig gekapselte Typen verfügbar. Dies wäre also nur ein Problem für die wenigen Benutzer, die dies tun möchten und diese Aufteilung wünschen.

Es ist also kein großer Verlust.

b) Nur eine Klasse im gesamten Vererbungsbaum kann nicht statische Datenelemente haben.

Der Grund dafür ist, warum sie das Standardlayout wieder standardisiert haben: gängige Praxis.

Es gibt keine gängige Praxis, wenn zwei Mitglieder eines Vererbungsbaums tatsächlich Dinge speichern. Einige stellen die Basisklasse vor die abgeleitete, andere machen es anders. Wie bestellen Sie die Mitglieder, wenn sie aus zwei Basisklassen stammen? Und so weiter. Compiler unterscheiden sich in diesen Fragen stark.

Dank der Null / Eins / Unendlich-Regel können Sie, sobald Sie sagen, dass Sie zwei Klassen mit Mitgliedern haben können, so viele sagen, wie Sie möchten. Dies erfordert das Hinzufügen vieler Layoutregeln, um damit umzugehen. Sie müssen sagen, wie Mehrfachvererbung funktioniert, welche Klassen ihre Daten vor andere Klassen stellen usw. Das sind viele Regeln für sehr geringen materiellen Gewinn.

Sie können nicht alles erstellen, das keine virtuellen Funktionen und ein Standardkonstruktor-Standardlayout hat.

und das erste nicht statische Datenelement kann nicht vom Typ einer Basisklasse sein (dies könnte gegen Aliasing-Regeln verstoßen).

Ich kann nicht wirklich mit diesem sprechen. Ich bin nicht genug in den Aliasing-Regeln von C ++ ausgebildet, um es wirklich zu verstehen. Dies hat jedoch damit zu tun, dass das Basismitglied dieselbe Adresse wie die Basisklasse selbst hat. Das ist:

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

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

Und das verstößt wahrscheinlich gegen die Aliasing-Regeln von C ++. Irgendwie.

Dies jedoch berücksichtigen: Wie nützlich könnte die Fähigkeit, dies zu tun , jemals wirklich sein? Da nur eine Klasse nicht statische Datenelemente haben kann, Derivedmuss diese Klasse sein (da sie Baseein Mitglied hat). So Base muss leer sein (von Daten). Und wenn Baseist leer, sowie eine Basisklasse ... warum überhaupt ein Datenmitglied?

Da Basees leer ist, hat es keinen Zustand. Alle nicht statischen Elementfunktionen tun also das, was sie tun, basierend auf ihren Parametern und nicht auf ihrem thisZeiger.

Also nochmal: kein großer Verlust.

Nicol Bolas
quelle
Danke für die Erklärung, es hilft sehr. Wahrscheinlich trotz static_cast<Base*>(&d)und &d.bvom gleichen Base*Typ, weisen sie auf verschiedene Dinge hin und verstoßen so gegen die Aliasing-Regel. Bitte korrigieren Sie mich.
Andriy Tylychko
1
und warum, wenn nur eine Klasse nicht statische Datenelemente haben kann, dann Derivedmuss diese Klasse sein?
Andriy Tylychko
3
@AndyT: Damit Deriveddas erste Mitglied seine Basisklasse sein kann, muss es zwei Dinge haben: eine Basisklasse und ein Mitglied . Und da nur eine Klasse in der Hierarchie Mitglieder haben kann (und immer noch Standardlayout ist), bedeutet dies, dass ihre Basisklasse keine Mitglieder haben kann.
Nicol Bolas
3
@AndyT, Ja, Sie haben im Wesentlichen Recht, IME, was die Aliasing-Regel betrifft. Zwei unterschiedliche Instanzen desselben Typs sind erforderlich, um unterschiedliche Speicheradressen zu haben. (Dies ermöglicht die Verfolgung der Objektidentität mit Speicheradressen.) Das Basisobjekt und das erste abgeleitete Element sind unterschiedliche Instanzen, daher müssen sie unterschiedliche Adressen haben, wodurch das Hinzufügen von Auffüllungen erzwungen wird, was sich auf das Layout der Klasse auswirkt. Wenn sie von unterschiedlicher Art wären, wäre das egal; Objekte mit unterschiedlichen Typen dürfen dieselbe Adresse haben (z. B. eine Klasse und ihr erstes Datenelement).
Adam H. Peterson
46

Änderungen in C ++ 17

Laden Sie die C ++ 17 International Standard endgültigen Entwurf hier .

Aggregate

C ++ 17 erweitert und erweitert Aggregate und Aggregatinitialisierung. Die Standardbibliothek enthält jetzt auch eine std::is_aggregateTypmerkmalsklasse. Hier ist die formale Definition aus Abschnitt 11.6.1.1 und 11.6.1.2 (interne Referenzen entfallen):

Ein Aggregat ist ein Array oder eine Klasse mit
- keinen vom Benutzer bereitgestellten, expliziten oder geerbten Konstruktoren,
- keinen privaten oder geschützten nicht statischen Datenelementen,
- keinen virtuellen Funktionen und
- keinen virtuellen, privaten oder geschützten Basisklassen.
[Hinweis: Die Aggregatinitialisierung ermöglicht keinen Zugriff auf Mitglieder oder Konstruktoren der geschützten und privaten Basisklasse. —End note]
Die Elemente eines Aggregats sind:
- für ein Array die Array-Elemente in aufsteigender Indexreihenfolge oder
- für eine Klasse die direkten Basisklassen in Deklarationsreihenfolge, gefolgt von den direkten nicht statischen Datenelementen, die dies nicht sind Mitglieder einer anonymen Gewerkschaft in Deklarationsreihenfolge.

Was hat sich geändert?

  1. Aggregate können jetzt öffentliche, nicht virtuelle Basisklassen haben. Darüber hinaus ist es nicht erforderlich, dass Basisklassen Aggregate sind. Wenn es sich nicht um Aggregate handelt, werden sie listeninitialisiert.
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. Explizite voreingestellte Konstruktoren sind nicht zulässig
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Vererbende Konstruktoren sind nicht zulässig
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Trivialklassen

Die Definition der Trivialklasse wurde in C ++ 17 überarbeitet, um mehrere Fehler zu beheben, die in C ++ 14 nicht behoben wurden. Die Änderungen waren technischer Natur. Hier ist die neue Definition bei 12.0.6 (interne Referenzen entfallen):

Eine trivial kopierbare Klasse ist eine Klasse:
- wobei jeder Kopierkonstruktor, Verschiebungskonstruktor, Kopierzuweisungsoperator und Verschiebungszuweisungsoperator entweder gelöscht oder trivial ist,
- die mindestens einen nicht gelöschten Kopierkonstruktor, Verschiebungskonstruktor, Kopierzuweisungsoperator hat, oder Verschiebungszuweisungsoperator, und
- der einen trivialen, nicht gelöschten Destruktor hat.
Eine triviale Klasse ist eine Klasse, die trivial kopierbar ist und einen oder mehrere Standardkonstruktoren hat, die alle entweder trivial oder gelöscht sind und von denen mindestens einer nicht gelöscht wird. [Hinweis: Insbesondere hat eine trivial kopierbare oder trivial Klasse keine virtuellen Funktionen oder virtuellen Basisklassen. - Endnote]

Änderungen:

  1. Unter C ++ 14 konnte eine Klasse, um trivial zu sein, keine nicht trivialen Kopier- / Verschiebungskonstruktor- / Zuweisungsoperatoren haben. Dann könnte ein implizit als standardmäßig deklarierter Konstruktor / Operator deklarierter und dennoch nicht trivial sein als gelöscht definiert werden, da die Klasse beispielsweise ein Unterobjekt vom Klassentyp enthielt, das nicht kopiert / verschoben werden konnte. Das Vorhandensein eines solchen nicht trivialen Konstruktors / Operators, der als gelöscht definiert ist, würde dazu führen, dass die gesamte Klasse nicht trivial ist. Ein ähnliches Problem bestand bei Destruktoren. C ++ 17 stellt klar, dass das Vorhandensein eines solchen Konstruktors / solcher Operatoren nicht dazu führt, dass die Klasse nicht trivial kopierbar und daher nicht trivial ist, und dass eine trivial kopierbare Klasse einen trivialen, nicht gelöschten Destruktor haben muss. DR1734 , DR1928
  2. In C ++ 14 konnte eine trivial kopierbare Klasse, daher eine trivial Klasse, jeden Kopier- / Verschiebungskonstruktor / Zuweisungsoperator als gelöscht deklarieren. Wenn eine solche Klasse auch ein Standardlayout wäre, könnte sie jedoch legal kopiert / verschoben werdenstd::memcpy . Dies war ein semantischer Widerspruch, da der Ersteller der Klasse durch die Definition aller Konstruktor- / Zuweisungsoperatoren als gelöscht eindeutig beabsichtigte, dass die Klasse nicht kopiert / verschoben werden konnte, die Klasse jedoch die Definition einer trivial kopierbaren Klasse erfüllte. Daher haben wir in C ++ 17 eine neue Klausel, die besagt, dass eine trivial kopierbare Klasse mindestens einen trivialen, nicht gelöschten (wenn auch nicht unbedingt öffentlich zugänglichen) Kopier- / Verschiebungskonstruktor / Zuweisungsoperator haben muss. Siehe N4148 , DR1734
  3. Die dritte technische Änderung betrifft ein ähnliches Problem mit Standardkonstruktoren. Unter C ++ 14 kann eine Klasse triviale Standardkonstruktoren haben, die implizit als gelöscht definiert wurden und dennoch eine triviale Klasse sind. Die neue Definition stellt klar, dass eine triviale Klasse mindestens einen trivialen, nicht gelöschten Standardkonstruktor haben muss. Sehen DR1496

Standard-Layout-Klassen

Die Definition des Standardlayouts wurde ebenfalls überarbeitet, um Fehlerberichte zu beheben. Auch hier waren die Änderungen technischer Natur. Hier ist der Text aus dem Standard (12.0.7). Interne Referenzen entfallen nach wie vor:

Eine Klasse S ist eine Standardlayoutklasse, wenn sie:
- keine nicht statischen Datenelemente vom Typ Nichtstandardlayoutklasse (oder Array solcher Typen) oder Referenz hat,
- keine virtuellen Funktionen und keine virtuellen Basisklassen hat,
- hat die gleiche Zugriffssteuerung für alle nicht statischen Datenelemente,
- hat keine Basisklassen mit nicht standardmäßigem Layout,
- hat höchstens ein Basisklassen-Unterobjekt eines bestimmten Typs,
- hat alle nicht statischen Datenelemente und Bitfelder in Die Klasse und ihre Basisklassen werden zuerst in derselben Klasse deklariert und
- haben kein Element der Menge M (S) von Typen (unten definiert) als Basisklasse.108
M (X) ist wie folgt definiert:
- Wenn X ein nicht vereinigter Klassentyp ohne (möglicherweise geerbte) nicht statische Datenelemente ist, ist die Menge M (X) leer. - Wenn X ein Nicht-Klassen- oder Nicht-Array-Typ ist, ist die Menge M (X) leer.
- Wenn X ein Klassentyp ohne Vereinigung ist, dessen erstes nicht statisches Datenelement den Typ X0 hat (wobei das Element eine anonyme Vereinigung sein kann), besteht die Menge M (X) aus X0 und den Elementen von M (X0).
- Wenn X ein Vereinigungstyp ist, ist die Menge M (X) die Vereinigung aller M (Ui) und die Menge, die alle Ui enthält, wobei jede Ui der Typ des i-ten nicht statischen Datenelements von X ist.
- Wenn X. ist ein Array-Typ mit dem Elementtyp Xe, die Menge M (X) besteht aus Xe und den Elementen von M (Xe).

[Hinweis: M (X) ist die Menge der Typen aller Unterobjekte, die nicht zur Basisklasse gehören und in einer Standardlayoutklasse garantiert einen Versatz von Null in X aufweisen. - Anmerkung zum Ende]
[Beispiel:

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
—Ende Beispiel]
108) Dies stellt sicher, dass zwei Unterobjekte, die denselben Klassentyp haben und zu demselben am meisten abgeleiteten Objekt gehören, nicht an derselben Adresse zugewiesen werden.

Änderungen:

  1. Es wurde klargestellt, dass die Anforderung, dass nur eine Klasse im Ableitungsbaum nicht statische Datenelemente "hat", sich auf eine Klasse bezieht, in der solche Datenelemente zuerst deklariert werden, nicht auf Klassen, in denen sie möglicherweise vererbt werden, und diese Anforderung auf nicht statische Bitfelder erweitert . Außerdem wurde klargestellt, dass eine Standard-Layout-Klasse "höchstens ein Basisklassen-Unterobjekt eines bestimmten Typs hat". Siehe DR1813 , DR1881
  2. Die Definition des Standardlayouts hat niemals zugelassen, dass der Typ einer Basisklasse mit dem ersten nicht statischen Datenelement identisch ist. Dies soll eine Situation vermeiden, in der ein Datenelement mit Offset Null denselben Typ wie eine Basisklasse hat. Der C ++ 17-Standard bietet eine strengere, rekursive Definition von "der Menge der Typen aller Unterobjekte, die nicht zur Basisklasse gehören und in einer Standardlayoutklasse garantiert einen Versatz von Null aufweisen", um solche Typen zu verbieten vom Typ einer Basisklasse. Siehe DR1672 , DR2120 .

Hinweis: Das C ++ - Standardkomitee beabsichtigte, die oben genannten Änderungen basierend auf Fehlerberichten auf C ++ 14 anzuwenden, obwohl die neue Sprache nicht im veröffentlichten C ++ 14-Standard enthalten ist. Es ist im C ++ 17-Standard.

ThomasMcLeod
quelle
Hinweis Ich habe gerade meine Antwort aktualisiert. Die Standardfehler bei Layoutänderungen haben den CD4-Status, was bedeutet, dass sie tatsächlich auf C ++ 14 angewendet werden. Aus diesem Grund enthielt meine Antwort sie nicht, da dies geschah, nachdem ich meine Antwort geschrieben hatte.
Shafik Yaghmour
Beachten Sie, dass ich eine Prämie für diese Frage gestartet habe.
Shafik Yaghmour
Danke @ShafikYaghmour. Ich werde den Status der Fehlerberichte überprüfen und meine Antwort entsprechend ändern.
ThomasMcLeod
@ShafikYaghmour, Nach einiger Überprüfung des C ++ 14-Prozesses und es scheint mir, dass, während diese DRs beim Rapperswil-Treffen im Juni 2014 "akzeptiert" wurden, die Sprache des Issaquah-Treffens im Februar 2014 zu C ++ 14 wurde. Siehe isocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting "In Übereinstimmung mit den ISO-Regeln haben wir keine Änderungen am C ++ - Arbeitspapier offiziell genehmigt." Vermisse ich etwas
Thomas McLeod
Sie haben den Status 'CD4', was bedeutet, dass sie im C ++ 14-Modus angewendet werden sollten.
Shafik Yaghmour
14

Was ändert sich in

Dem Rest des klaren Themas dieser Frage folgend, ändert sich die Bedeutung und Verwendung von Aggregaten mit jedem Standard weiter. Es gibt einige wichtige Änderungen am Horizont.

Typen mit vom Benutzer deklarierten Konstruktoren P1008

In C ++ 17 ist dieser Typ immer noch ein Aggregat:

struct X {
    X() = delete;
};

Und wird daher X{}immer noch kompiliert, da dies eine aggregierte Initialisierung ist - kein Konstruktoraufruf. Siehe auch: Wann ist ein privater Konstruktor kein privater Konstruktor?

In C ++ 20 ändert sich die Einschränkung von der Anforderung:

Keine vom Benutzer bereitgestellten explicitoder geerbten Konstruktoren

zu

Keine vom Benutzer deklarierten oder geerbten Konstruktoren

Dies wurde in den C ++ 20-Arbeitsentwurf übernommen . Weder das XHier noch das Cin der verknüpften Frage werden in C ++ 20 Aggregate sein.

Dies führt auch zu einem Jojo-Effekt mit dem folgenden Beispiel:

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

In C ++ 11/14, Bwar nicht ein Aggregat aufgrund der Basisklasse, so B{}führt die Mehrwert-Initialisierung die Anrufe , B::B()welche Anrufe A::A()zu einem Punkt , wo sie zugänglich ist. Das war gut geformt.

In C ++ 17 wurde es Bzu einem Aggregat, da Basisklassen zulässig waren, die eine B{}Aggregatinitialisierung durchführten. Dies erfordert eine Initialisierung der Kopierliste Avon {}, aber von außerhalb des Kontexts B, auf den nicht zugegriffen werden kann. In C ++ 17 ist dies schlecht geformt ( auto x = B();wäre aber in Ordnung).

In C ++ 20 ist jetzt aufgrund der obigen Regeländerung Bwieder kein Aggregat mehr (nicht wegen der Basisklasse, sondern wegen des vom Benutzer deklarierten Standardkonstruktors - obwohl dieser standardmäßig ist). Wir gehen also zurück zum BKonstruktor, und dieses Snippet wird wohlgeformt.

Initialisieren von Aggregaten aus einer Liste von Werten in Klammern P960

Ein häufiges Problem ist die Verwendung von emplace()Konstruktoren im Stil mit Aggregaten:

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

Dies funktioniert nicht, da emplaceversucht wird X(1, 2), die ungültige Initialisierung effektiv durchzuführen . Die typische Lösung besteht darin, einen Konstruktor hinzuzufügen X, aber mit diesem Vorschlag (der sich derzeit durch Core arbeitet) haben Aggregate effektiv synthetisierte Konstruktoren, die das Richtige tun - und sich wie reguläre Konstruktoren verhalten. Der obige Code wird in C ++ 20 unverändert kompiliert.

Klassenvorlagen-Argumentabzug (CTAD) für Aggregate P1021 (speziell P1816 )

In C ++ 17 wird dies nicht kompiliert:

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

Point p{1, 2}; // error

Benutzer müssten für alle aggregierten Vorlagen einen eigenen Abzugsleitfaden schreiben:

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

Aber da dies in gewissem Sinne "das Offensichtliche" ist und im Grunde nur ein Boilerplate ist, wird die Sprache dies für Sie tun. Dieses Beispiel wird in C ++ 20 kompiliert (ohne dass der vom Benutzer bereitgestellte Abzugsleitfaden erforderlich ist).

Barry
quelle
Obwohl ich abstimmen werde, fühlt es sich etwas früh an, dies hinzuzufügen. Ich weiß nicht, dass irgendetwas Wichtiges herauskommt, das dies ändern würde, bevor C ++ 2x fertig ist.
Shafik Yaghmour
@ShafikYaghmour Ja, wahrscheinlich viel zu früh. Angesichts der Tatsache, dass SD die Frist für neue Sprachfunktionen war, sind dies die einzigen zwei während des Flugs, die mir bekannt sind - im schlimmsten Fall blockiere ich nur einen dieser Abschnitte später? Ich habe gerade die Frage mit dem Kopfgeld aktiv gesehen und dachte, es sei ein guter Zeitpunkt, sich einzuschalten, bevor ich es vergesse.
Barry
Ich verstehe, ich wurde ein paar Mal für ähnliche Fälle versucht. Ich mache mir immer Sorgen, dass sich etwas Wichtiges ändern wird und ich es am Ende neu schreiben muss.
Shafik Yaghmour
@ShafikYaghmour Hier scheint sich nichts zu ändern :)
Barry
Ich hoffe, dass dies jetzt aktualisiert wird und C ++ 20 bereits veröffentlicht ist
Noone AtAll