it-swarm.com.de

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

Diese FAQ handelt von Aggregaten und PODs und deckt das folgende Material ab:

  • Was sind Aggregate?
  • Was sind [~ # ~] pod [~ # ~] s (Plain Old Data)?
  • Wie hängen sie zusammen?
  • Wie und warum sind sie besonders?
  • Was ändert sich für C++ 11?
515
Armen Tsirunyan

Wie man liest:

Dieser Artikel ist ziemlich lang. Wenn Sie sowohl Aggregate als auch PODs (Plain Old Data) kennenlernen möchten, nehmen Sie sich Zeit und lesen Sie sie durch. Wenn Sie nur an Aggregaten interessiert sind, lesen Sie nur den ersten Teil. Wenn Sie sich nur für PODs interessieren, müssen Sie zuerst die Definition, die Implikationen und die Beispiele für Aggregate lesen und dann may zu PODs springen, aber ich würde trotzdem 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 bearbeite.

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, lasst uns diese Definition analysieren. Zuallererst ist jedes Array ein Aggregat. Eine Klasse kann auch ein Aggregat sein, wenn ... warte! Über Strukturen oder Gewerkschaften wird nichts gesagt, können sie nicht Aggregate sein? Ja, sie können. In C++ bezieht sich der Begriff class auf alle Klassen, Strukturen und Gewerkschaften. Eine Klasse (oder Struktur oder Vereinigung) ist dann und nur 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 statische Datenelemente. Sie können so viele private und geschützte Member-Funktionen (aber keine Konstruktoren) sowie so viele private oder geschützte static Datenelemente und Elemente funktionieren wie Sie möchten und verstoßen nicht gegen die Regeln für aggregierte Klassen

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

  • Ein Array ist ein Aggregat, auch wenn es sich um ein Array eines nicht aggregierten Klassentyps 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 dies Aggregate sind. Fangen wir also mit ihnen an.

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

if (m == n)
das ith Element des Arrays wird mit a initialisiertich
sonst wenn (m <n)
Die ersten m Elemente des Arrays werden mit a initialisiert1, ein2, …, einm und die anderen n - m - Elemente sind nach Möglichkeit value-initialized (Erklärung des Begriffs siehe unten)
sonst wenn (m> n)
Der Compiler gibt einen Fehler aus
else (dies ist der Fall, wenn n überhaupt nicht spezifiziert ist wie int a[] = {1, 2, 3};)
Es wird angenommen, dass die Größe des Arrays (n) gleich m ist, also entspricht int a[] = {1, 2, 3};int a[3] = {1, 2, 3};

Wenn ein Objekt vom Skalartyp (bool, int, char, double, Zeiger usw.) Wert ist -initialized bedeutet, dass es für diesen Typ mit 0 initialisiert wird (false für bool, 0.0 für double usw.). Wenn ein Objekt des Klassentyps mit einem benutzerdefinierten Standardkonstruktor durch einen Wert initialisiert wird, wird sein Standardkonstruktor aufgerufen. Wenn der Standardkonstruktor implizit definiert ist, werden alle nicht statischen Member rekursiv mit Werten initialisiert. Diese Definition ist ungenau und ein bisschen falsch, aber sie sollte Ihnen die Grundidee geben. Eine Referenz kann nicht mit einem Wert initialisiert 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. Ziemlich genau so. 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 mit Werten initialisiert. Wenn es nicht möglich ist, einen der nicht explizit initialisierten Member mit einem Wert zu initialisieren, erhalten wir einen Fehler beim Kompilieren. Wenn mehr Initialisierer als erforderlich vorhanden sind, wird ebenfalls ein Fehler beim Kompilieren 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}};

Im obigen Beispiel wird y.c Mit 'a', y.x.i1 Mit 10, y.x.i2 Mit 20, y.i[0] Mit 20, y.i[1] Mit 30 Und y.f Wird wertinitialisiert, dh mit 0.0 Initialisiert. Das geschützte statische Element d wird überhaupt nicht initialisiert, weil es static ist.

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 über die Verwendung von Gewerkschaften nachzudenken (ihre Verwendung kann sehr gefährlich sein und muss sorgfältig überlegt werden), könnten Sie die Regeln für Gewerkschaften im Standard selbst nachschlagen :).

Nachdem wir nun wissen, was an Aggregaten besonders ist, wollen wir versuchen, die Einschränkungen für Klassen zu verstehen. das ist, warum sie da sind. Wir sollten verstehen, dass die memberweise Initialisierung mit geschweiften Klammern impliziert, dass die Klasse nicht mehr als die Summe ihrer Member ist. Wenn ein benutzerdefinierter Konstruktor vorhanden ist, bedeutet dies, dass der Benutzer einige zusätzliche Arbeiten ausführen muss, um die Member 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 die Klammerinitialisierung unzureichend wäre. Sie könnten den Rest der Einschränkungen auf ähnliche Weise wie eine Übung herausfinden :).

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

Was sind PODs und warum sind sie besonders?

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 hat und keinen benutzerdefinierten Kopierzuweisungsoperator und -nr benutzerdefinierter Destruktor. In ähnlicher Weise ist eine POD-Union eine aggregierte Union, die keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Union (oder Array solcher Typen) oder Referenz sowie keinen benutzerdefinierten Kopierzuweisungsoperator enthält 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 weg (aus den gleichen Gründen wie oben) und formulieren sie etwas klarer:

Eine Aggregatklasse wird als POD bezeichnet, wenn sie keinen benutzerdefinierten Operator und Destruktor für die Zuweisung von Kopien enthält und keiner ihrer nicht statischen Member eine Nicht-POD-Klasse, ein Array von Nicht-POD oder eine Referenz ist.

Was bedeutet diese Definition? (Habe ich erwähnt? [~ # ~] Pod [~ # ~] steht für Plain Old Daten?)

  • Alle POD-Klassen sind Aggregate, oder umgekehrt: Wenn eine Klasse kein Aggregat ist, ist sie mit Sicherheit kein POD
  • Klassen können genau wie Strukturen PODs sein, obwohl der Standardbegriff in beiden Fällen POD-struct ist
  • Genau wie bei Aggregaten spielt es keine Rolle, über welche statischen Member die Klasse verfügt

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, skalare Typen und Arrays dieser Typen werden gemeinsam als POD-Typen. bezeichnet.
PODs sind in vielerlei Hinsicht etwas Besonderes. Ich werde nur einige Beispiele nennen.

  • POD-Klassen sind den 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 Ihre exportierten Funktionen dazu zu bringen, nur Parameter vom Typ POD zu übernehmen und zurückzugeben.

  • Die Lebensdauer von Objekten eines Nicht-POD-Klassentyps 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 beim memcpy Hinzufügen des Inhalts Ihres Objekts zu einem Array von char oder unsigned char und anschließend memcpy der Inhalt in Ihr Objekt zurückgegeben wird behält das Objekt seinen ursprünglichen Wert. Beachten Sie, dass für Objekte, die keine POD-Typen sind, keine solche Garantie besteht. Außerdem können Sie POD-Objekte mit memcpy sicher kopieren. Im folgenden Beispiel wird angenommen, 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), mit goto von einem Punkt, an dem eine Variable noch nicht im Gültigkeitsbereich war, zu einem Punkt zu springen, an dem sie bereits im Gültigkeitsbereich liegt. Diese Einschränkung gilt nur, wenn die Variable nicht vom Typ POD ist. Im folgenden Beispiel ist f() schlecht geformt, während g() gut geformt ist. Beachten Sie, dass der Microsoft-Compiler 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 ist 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_cast Von A* Zu T* Wechseln und den Zeiger auf das erste Element und umgekehrt setzen 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.

531
Armen Tsirunyan

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

Aggregate

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

Ein Aggregat ist ein Array oder eine Klasse (Klausel 9) ohne vom Benutzer bereitgestellte Konstruktoren (12.1), no Klammer- oder Gleichheitsinitialisierer für nicht statische Datenelemente (9.2), no private or geschützte nicht statische Datenelemente (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 jedoch keine vom Benutzer bereitgestellten -Konstruktoren. Ist da ein Unterschied? Ja, denn jetzt können Sie Konstruktoren deklarieren und Standard sie:

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

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

  2. Jetzt kann ein Aggregat keine Klammer-oder-Gleich-Initialisierer 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 sie im Grunde genommen der Bereitstellung Ihres eigenen Standardkonstruktors entspricht.

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

Was ist mit PODs?

PODs haben viele Veränderungen durchgemacht. 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 grundlegend geändert.

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

  1. Es unterstützt die statische Initialisierung und
  2. Durch das Kompilieren eines POD in C++ erhalten Sie dasselbe Speicherlayout wie bei einer in C kompilierten Struktur.

Aus diesem Grund wurde die Definition in zwei unterschiedliche Konzepte unterteilt: trivial Klassen und Standard-Layout Klassen, da diese nützlicher sind als POD. Der Standard verwendet jetzt selten den Begriff POD und bevorzugt die spezifischeren Konzepte trivial und standard-layout.

Die neue Definition besagt im Grunde, 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-Union-Klasse, die sowohl eine Trivialklasse als auch eine Standardlayoutklasse ist und keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Union (oder Array solcher Typen) enthält. In ähnlicher Weise ist eine POD-Union eine Union, die sowohl eine Trivialklasse als auch eine Standardlayoutklasse ist und keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Union (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 einfach kopierbar ist (eine Obermenge von einfachen Klassen), ist es in Ordnung, ihre Darstellung mit Dingen wie memcpy über die Stelle zu kopieren und zu erwarten, dass das Ergebnis dasselbe ist.

Der Standard definiert eine Trivialklasse wie folgt:

Eine einfach kopierbare Klasse ist eine Klasse, die:

- keine nicht trivialen Kopierkonstruktoren hat (12.8),

- keine nicht-trivialen Zugkonstruktoren hat (12.8),

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

- hat keine nicht-trivialen Zugzuweisungsoperatoren (13.5.3, 12.8) und

- hat einen trivialen Destruktor (12.4).

Eine Trivialklasse ist eine Klasse, die einen Trivialstandardkonstruktor (12.1) hat und trivial kopierbar ist.

[Anmerkung: Insbesondere hat eine trivial kopierbare oder triviale Klasse keine virtuellen Funktionen oder virtuellen Basisklassen .- Anmerkung am Ende]

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

Ein Copy/Move-Konstruktor für die 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, und

- 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 Konstruktor zum Kopieren/Verschieben nicht trivial.

Grundsätzlich bedeutet dies, dass ein Konstruktor zum Kopieren oder Verschieben 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-/Verschiebungs-Zuweisungsoperators ist sehr ähnlich und ersetzt einfach den Word- "Konstruktor" durch "Zuweisungsoperator".

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

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

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

Standardentwurf

Standard-Layout ist die zweite Eigenschaft. Der Standard erwähnt, dass diese für die Kommunikation mit anderen Sprachen nützlich sind, da 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 Zugriffskontrolle haben müssen. Früher mussten diese alle öffentlich sein, aber 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 enthalten, und das erste nicht statische Datenelement kann keinen Basisklassentyp haben (dies kann gegen Aliasing-Regeln verstoßen). Andernfalls handelt es sich nicht um eine Standardlayoutklasse.

So sieht die Definition im Standardtext aus:

Eine Standard-Layout-Klasse ist eine Klasse, die:

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

- für alle nicht statischen Datenmitglieder die gleiche Zugangskontrolle (Ziffer 11) hat,

- keine Nicht-Standard-Layout-Basisklassen 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 desselben Typs wie das erste nicht statische Datenelement.

Eine Standard-Layout-Struktur ist eine Standard-Layout-Klasse, die mit der Klassenschlüsselstruktur oder der Klassenschlüsselklasse definiert wird.

Eine Standardlayoutunion ist eine Standardlayoutklasse, die mit der Klassenschlüsselunion definiert wird.

[Hinweis: Standard-Layout-Klassen 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 einfaches oder 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;
431

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

Wir können auf den Draft C++ 14 Standard als Referenz verweisen.

Aggregate

Dies wird in Abschnitt 8.5.1 Aggregate behandelt, der die folgende Definition enthält:

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

Die einzige Änderung besteht jetzt darin, dass das Hinzufügen von Member-Initialisierern in der Klasse eine Klasse nicht zu einem Nichtaggregat macht. Das folgende Beispiel aus C++ 11-Aggregatinitialisierung für Klassen mit Member-In-Pace-Initialisierern :

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: Elementinitialisierer und Aggregate behandelt, das die folgende Zusammenfassung enthält:

Bjarne Stroustrup und Richard Smith haben ein Problem bezüglich der Gesamtinitialisierung und der nicht zusammenarbeitenden Elementinitialisierer angesprochen. In diesem Artikel wird vorgeschlagen, das Problem zu beheben, indem der von Smith vorgeschlagene Wortlaut übernommen wird, der eine Einschränkung beseitigt, die Aggregate nicht mit Initialisierern für Mitglieder haben können.

POD bleibt gleich

Die Definition für POD ( plain old data struct wird in Abschnitt 9 Classes das sagt:

Eine POD-Struktur110 ist eine Nicht-Union-Klasse, die sowohl eine Trivialklasse als auch eine Standardlayoutklasse ist und keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Union (oder Array solcher Typen) enthält. In ähnlicher Weise ist eine POD-Union eine Union, die sowohl eine Trivialklasse als auch eine Standardlayoutklasse ist und keine nicht statischen Datenelemente vom Typ Nicht-POD-Struktur, Nicht-POD-Union (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.

Standard-Layout-Änderungen für C++ 14

Wie in den Kommentaren vermerkt, basiert pod auf der Definition von Standard-Layout und das änderte sich für C++ 14, aber dies geschah über Fehlerberichte, die später auf C++ 14 angewendet wurden.

Es gab drei DRs:

Also Standard-Layout ging von diesem Pre C++ 14 aus:

Eine Standard-Layout-Klasse ist eine Klasse, die:

  • (7.1) keine nicht statischen Datenelemente vom Typ Nicht-Standard-Layout-Klasse (oder Array solcher Typen) oder Referenz hat,
  • (7.2) hat keine virtuellen Funktionen ([class.virtual]) und keine virtuellen Basisklassen ([class.mi]),
  • (7.3) hat die gleiche Zugriffskontrolle (Klausel [class.access]) für alle nicht statischen Datenmitglieder,
  • (7.4) hat keine Nicht-Standard-Layout-Basisklassen,
  • (7.5) entweder keine nicht statischen Datenelemente in der am häufigsten 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

To dies in C++ 14 :

Eine Klasse S ist eine Standard-Layout-Klasse, wenn sie:

  • (3.1) keine nicht statischen Datenelemente vom Typ Nicht-Standard-Layout-Klasse (oder Array solcher Typen) oder Referenz hat,
  • (3.2) hat keine virtuellen Funktionen und keine virtuellen Basisklassen,
  • (3.3) hat die gleiche Zugriffskontrolle für alle nicht statischen Datenmitglieder,
  • (3.4) hat keine Nicht-Standard-Layout-Basisklassen,
  • (3.5) höchstens ein Basisklassen-Unterobjekt eines bestimmten Typs hat,
  • (3.6) hat alle nicht statischen Datenelemente und Bitfelder in der Klasse und ihren 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) definiert ist wie folgt.104 [Anmerkung: M(X) ist die Menge der Typen aller Nicht-Basisklassen-Unterobjekte, die in X einen Versatz von Null haben können. - Endnote]
    • (3.7.1) Wenn X ein nicht gewerkschaftlicher Klassentyp ohne (möglicherweise geerbte) nicht statische Datenelemente ist, ist die Menge M(X) leer.
    • (3.7.2) Wenn X ein Klassentyp ohne Vereinigung 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 Element 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 von allen M(Ui) und der Menge, die alle enthält Ui, wobei jedes 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-, Nicht-Array-Typ ist, ist die Menge M(X) leer.
96
Shafik Yaghmour

können Sie bitte folgende Regeln ausarbeiten:

Ich werde es versuchen:

a) Standard-Layout-Klassen müssen alle nicht statischen Datenelemente mit derselben Zugriffssteuerung haben

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

Die Begründung für sie geht auf die Begründung für die Unterscheidung zwischen "Standardlayout" und "Nicht-Standardlayout". Nämlich, um dem Compiler die Freiheit zu geben, zu wählen, wie die Dinge in den Speicher gestellt werden sollen. Es geht nicht nur um vtable-Zeiger.

Als sie C++ 98 standardisierten, mussten sie im Grunde voraussagen, wie die Leute es implementieren würden. Sie hatten zwar einige Implementierungserfahrungen mit verschiedenen C++ - Varianten, waren sich jedoch nicht sicher. Deshalb 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 einen großen Spielraum beim Layout der Mitglieder für die meisten Klassen. Grundsätzlich sollten POD-Typen Sonderfälle sein, die Sie aus einem bestimmten Grund geschrieben haben.

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

Die Regeln des Standard-Layouts sind mehr oder weniger die gängige Praxis: Die meisten Compiler mussten nicht wirklich viel oder gar nichts ändern, um sie zu implementieren (abgesehen von vielleicht etwas für die entsprechenden Typmerkmale).

Nun, wenn es um public/private geht, liegen die Dinge anders. Die Freiheit, die Reihenfolge der Mitglieder, die public oder private sind, zu ändern, kann für den Compiler von Bedeutung sein, insbesondere beim Debuggen von Builds. Und da das Standardlayout die Kompatibilität mit anderen Sprachen voraussetzt, kann das Layout in Debug- und Release-Versionen 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 ohnehin private sind. Sie machen öffentliche Datenmember in der Regel nicht für vollständig gekapselte Typen verfügbar. Das wäre also nur ein Problem für die wenigen Benutzer, die das wollen, die diese Aufteilung wollen.

Es ist also kein großer Verlust.

b) nur eine Klasse im gesamten Vererbungsbaum darf 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 es darum geht, zwei Mitglieder eines Vererbungsbaums zu haben, die tatsächlich Dinge speichern. Einige setzen die Basisklasse vor die abgeleitete, andere tun es auf die andere Weise. Wie bestellen Sie die Mitglieder, wenn sie aus zwei Basisklassen stammen? Und so weiter. Compiler gehen in diesen Fragen sehr weit auseinander.

Dank der Null-/Eins-/Unendlichkeitsregel können Sie, sobald Sie sagen, dass Sie zwei Klassen mit Mitgliedern haben können, so viele sagen, wie Sie möchten. Dazu müssen viele Layoutregeln hinzugefügt werden. Sie müssen sagen, wie die Mehrfachvererbung funktioniert, welche Klassen ihre Daten vor andere Klassen stellen usw. Das sind viele Regeln für sehr wenig materiellen Gewinn.

Sie können nicht alles erstellen, was nicht über virtuelle Funktionen und ein Standardlayout für Konstruktoren verfügt.

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 ausreichend in den Aliasing-Regeln von C++ geschult, um das wirklich zu verstehen. Dies hat jedoch etwas mit der Tatsache 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.

Bedenken Sie jedoch Folgendes: Wie nützlich könnte die Möglichkeit sein, dies jemals tatsächlich zu tun ? Da nur eine Klasse nicht statische Datenelemente haben kann, muss Derived diese Klasse sein (da sie ein Base als Element hat). Also muss Base leer sein (von Daten). Und wenn Base leer ist, sowie eine Basisklasse ... warum hat sie überhaupt ein Datenelement?

Da Base leer ist, hat es keinen Status. Alle nicht statischen Elementfunktionen tun also das, was sie tun, basierend auf ihren Parametern, nicht auf ihrem this - Zeiger.

Also nochmal: kein großer Verlust.

44
Nicol Bolas

Änderungen in C++ 17

Laden Sie den endgültigen Entwurf des C++ 17 International Standard herunter hier .

Aggregate

C++ 17 erweitert und verbessert Aggregate und Aggregatinitialisierung. Die Standardbibliothek enthält jetzt auch eine Merkmalsklasse vom Typ std::is_aggregate. Hier ist die formale Definition aus den Abschnitten 11.6.1.1 und 11.6.1.2 (interne Verweise entfallen):

Ein Aggregat ist ein Array oder eine Klasse mit
- keine vom Benutzer bereitgestellten, expliziten oder geerbten Konstruktoren,
- keine privaten oder geschützten nicht statischen Datenmitglieder,
- keine virtuellen Funktionen und
- keine virtuellen, privaten oder geschützten Basisklassen.
[Hinweis: Die aggregierte Initialisierung ermöglicht keinen Zugriff auf Mitglieder oder Konstruktoren der geschützten und privaten Basisklasse. —Ende Notiz]
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 keine Mitglieder einer anonymen Union sind, 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 mit einer Liste initialisiert.
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 Standardkonstruktoren 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 in 12.0.6 (interne Referenzen entfernt):

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

Änderungen:

  1. Unter C++ 14 konnte eine Klasse keine nicht-trivialen Kopier-/Verschiebekonstruktor-/Zuweisungsoperatoren haben, damit sie trivial war. Ein implizit deklariert als voreingestellter Konstruktor/Operator könnte jedoch nicht trivial und dennoch definiert als gelöscht sein, da die Klasse beispielsweise ein Unterobjekt von enthielt Klassentyp, der 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. In C++ 17 wird klargestellt, dass das Vorhandensein solcher Konstruktoren/Operatoren nicht dazu führt, dass die Klasse nicht trivial kopierbar und somit 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 einfach kopierbare Klasse, also eine einfache Klasse, jeden Konstruktor zum Kopieren/Verschieben/Zuweisen als gelöscht deklarieren. Wenn eine solche Klasse auch ein Standardlayout wäre, könnte sie jedoch mit std::memcpy Legal kopiert/verschoben werden. 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 weiterhin die Definition einer trivial kopierbaren Klasse erfüllte. Daher haben wir in C++ 17 eine neue Klausel, die besagt, dass trivial kopierbare Klassen mindestens einen trivialen, nicht gelöschten (wenn auch nicht unbedingt öffentlich zugänglichen) Konstruktor/Zuweisungsoperator zum Kopieren/Verschieben haben müssen. Siehe N4148 , DR1734
  3. Die dritte technische Änderung betrifft ein ähnliches Problem mit Standardkonstruktoren. Unter C++ 14 könnte eine Klasse einfache Standardkonstruktoren haben, die implizit als gelöscht definiert wurden, aber dennoch eine einfache Klasse sind. Die neue Definition stellt klar, dass eine einfache Klasse mindestens einen einfachen, nicht gelöschten Standardkonstruktor haben muss. Siehe DR1496

Standard-Layout Klassen

Die Definition des Standard-Layouts wurde ebenfalls überarbeitet, um Fehlerberichte zu adressieren. Auch hier waren die Änderungen technischer Natur. Hier ist der Text aus dem Standard (12.0.7). Interne Verweise werden wie bisher weggelassen:

Eine Klasse S ist eine Standard-Layout-Klasse, wenn sie:
- hat keine nicht statischen Datenelemente vom Typ Nicht-Standard-Layout-Klasse (oder Array solcher Typen) oder Referenz,
- hat keine virtuellen Funktionen und keine virtuellen Basisklassen,
- hat die gleiche Zugriffskontrolle für alle nicht statischen Datenmitglieder,
- hat keine Nicht-Standard-Layout-Basisklassen,
- hat höchstens ein Basisklassen-Unterobjekt eines bestimmten Typs,
- hat alle nicht statischen Datenelemente und Bitfelder in der Klasse und ihren Basisklassen, die zuerst in derselben Klasse deklariert wurden, und
- hat kein Element der Menge M(S) von Typen (unten definiert) als Basisklasse.108
M (X) ist wie folgt definiert:
- Wenn X ein nicht gewerkschaftlicher Klassentyp ohne (möglicherweise geerbte) nicht statische Datenelemente ist, ist die Menge M(X) leer.
- Wenn X ein Klassentyp ohne Gewerkschaft ist, dessen erstes nicht statisches Datenelement den Typ X0 hat (wobei das Element eine anonyme Gewerkschaft sein kann), wird die Menge M(X) = besteht aus X0 und den Elementen von M (X0).
- Wenn X ein Vereinigungstyp ist, ist die Menge M(X) die Vereinigung aller M(Ui) und der Menge, die enthält all Ui, wobei jedes Ui der Typ des i-ten nicht statischen Datenelements von X ist.
- Wenn X ein Array-Typ mit dem Elementtyp Xe ist, besteht die Menge M(X) aus Xe und den Elementen von M (Xe).
- Wenn X ein Nicht-Klassen-, Nicht-Array-Typ ist, ist die Menge M(X) leer.
[Anmerkung: M(X) ist die Menge der Typen aller Nicht-Basisklassen-Unterobjekte, für die in einer Standardlayoutklasse ein Versatz von Null garantiert ist in X. —Ende Notiz]
[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
- Beispiel beenden]
108) Dies stellt sicher, dass zwei Unterobjekte, die denselben Klassentyp haben und zu demselben am häufigsten abgeleiteten Objekt gehören, nicht an derselben Adresse zugeordnet 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 und nicht auf Klassen, in denen sie möglicherweise vererbt werden, und diese Anforderung auf nicht statische Bitfelder erweitert . Es wurde auch klargestellt, dass eine Standard-Layout-Klasse "höchstens ein Basisklassen-Unterobjekt eines bestimmten Typs hat". Siehe DR181 , DR1881
  2. Die Definition des Standardlayouts hat es nie zugelassen, dass der Typ einer Basisklasse dem Typ des ersten nicht statischen Datenelements entspricht. Es soll vermieden werden, dass ein Datenelement mit dem Offset Null den gleichen Typ wie eine Basisklasse hat. Der C++ 17-Standard enthält eine strengere, rekursivere Definition der "Menge der Typen aller Nicht-Basisklassen-Unterobjekte, für die in einer Standardlayoutklasse ein Offset von Null garantiert ist", um solche Typen zu verbieten vom Typ einer Basisklasse. Siehe DR1672 , DR212 .

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

33
ThomasMcLeod

Was wird sich für C++ 20 ändern?

Dies ist noch zu früh, daher kann sich ein Teil dieser Antwort in Zukunft ändern. Nach dem Rest des klaren Themas dieser Frage ändern sich die Bedeutung und Verwendung von Aggregaten mit jeder Norm weiter. Es gibt mehrere wichtige Änderungen am Horizont.

Typen mit benutzerdefinierten Konstruktoren P1008

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

struct X {
    X() = delete;
};

Daher wird X{} Immer noch kompiliert, da dies eine Gesamtinitialisierung ist - kein Konstruktoraufruf. Siehe auch: Wann ist ein privater Konstruktor kein privater Konstruktor?

In C++ 20 ändert sich die Einschränkung, da Folgendes erforderlich ist:

keine vom Benutzer angegebenen, explicit oder geerbten Konstruktoren

zu

keine vom Benutzer deklarierten oder geerbten Konstruktoren

Dies wurde in den C++ 20-Arbeitsentwurf übernommen. Weder das X hier noch das C in der verknüpften Frage sind in C++ 20 Aggregate.

Dies ergibt auch einen Jojo-Effekt mit dem folgenden Beispiel:

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

In C++ 11/14 war B kein Aggregat aufgrund der Basisklasse, daher wird B{} Ausgeführt Wertinitialisierung, die B::B() aufruft, die A::A() aufruft, an einem Punkt, an dem darauf zugegriffen werden kann. Das war wohlgeformt.

In C++ 17 wurde B zu einem Aggregat, da Basisklassen zulässig waren, die eine Aggregatinitialisierung von B{} Durchführten. Dies erfordert eine Kopierlisteninitialisierung eines A von {}, Jedoch von außerhalb des Kontexts von B, auf den nicht zugegriffen werden kann. In C++ 17 ist dies nicht korrekt (auto x = B(); wäre jedoch in Ordnung).

In C++ 20 ist B aufgrund der oben genannten Regeländerung wieder kein Aggregat mehr (nicht wegen der Basisklasse, sondern wegen des vom Benutzer deklarierten Standardkonstruktors - obwohl er standardmäßig verwendet wird). . Wir kehren also zum Konstruktor von B zurück, und dieses Snippet wird wohlgeformt.

Initialisieren von Aggregaten aus einer Liste von Werten in Klammern P96

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

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

Dies funktioniert nicht, da emplace versucht, die nicht gültige Initialisierung X(1, 2) effektiv auszuführen. Die typische Lösung besteht darin, X einen Konstruktor hinzuzufügen. Mit diesem Vorschlag (der sich derzeit durch Core bewegt) verfügen Aggregate jedoch effektiv über synthetisierte Konstruktoren, die das Richtige tun - und sich wie reguläre Konstruktoren verhalten. Der obige Code wird in C++ 20 so kompiliert, wie er ist (vorausgesetzt, diese Funktion wird genehmigt, was wahrscheinlich ist).

Argumentableitung für Klassenvorlagen (CTAD) für Aggregate P1021

In C++ 17 wird Folgendes 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 genommen nur ein Kessel ist, wird die Sprache dies für Sie tun. Diese Änderung wurde von Evolution im November 2018 genehmigt, sodass das obige Beispiel wahrscheinlich in C++ 20 kompiliert wird (ohne dass der vom Benutzer bereitgestellte Leitfaden zum Abzug erforderlich ist).

8
Barry