Este FAQ befasst sich mit Aggregaten und PODs und deckt die folgenden Themen 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?
Este FAQ befasst sich mit Aggregaten und PODs und deckt die folgenden Themen ab:
Dieser Artikel ist ziemlich lang. Wenn Sie sowohl über Aggregate als auch über PODs (Plain Old Data) Bescheid wissen wollen, nehmen Sie sich Zeit und lesen Sie ihn. Wenn Sie nur an den Aggregaten interessiert sind, lesen Sie nur den ersten Teil. Wenn Sie nur an PODs interessiert sind, müssen Sie zuerst die Definition, die Auswirkungen und die Beispiele von Aggregaten lesen und dann Mai zu den PODs zu 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 unerlässlich. Wenn Sie irgendwelche Fehler finden (auch kleinere, einschließlich Grammatik, Stilistik, Formatierung, Syntax usw.), hinterlassen Sie bitte einen Kommentar, ich werde ihn bearbeiten.
Diese Antwort gilt für C++03. Für andere C++-Standards siehe:
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), keine privaten oder geschützten nicht-statischen Datenelemente (Abschnitt 11), keine Basisklassen (Abschnitt 10), und keine virtuellen Funktionen (10.3).
Also, OK, analysieren wir diese Definition. Zunächst einmal ist jedes Array ein Aggregat. Eine Klasse kann auch ein Aggregat sein, wenn Moment! Es wird nichts über Structs oder Unions gesagt, können die nicht auch Aggregate sein? Ja, das können sie. In C++ ist der Begriff class
bezieht sich auf alle Klassen, Structs und Unions. Eine Klasse (oder Struktur oder Union) ist also nur dann ein Aggregat, wenn sie die Kriterien der obigen Definitionen erfüllt. Was implizieren diese Kriterien?
Das bedeutet nicht, dass eine Aggregatklasse keine Konstruktoren haben kann, sie kann sogar 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 Mitgliedsfunktionen (aber keine Konstruktoren) sowie so viele private oder geschützte statisch Datenmitglieder und Mitgliedsfunktionen nach Belieben zu verwenden und die Regeln für Aggregatklassen nicht zu verletzen
Eine Aggregatklasse kann einen benutzerdeklarierten/benutzerdefinierten Kopierzuweisungsoperator und/oder Destruktor haben
Ein Array ist ein Aggregat, auch wenn es sich um ein Array einer nicht-aggregierten Klasse 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
};
Sie haben die Idee. Nun wollen wir sehen, was Aggregate so besonders macht. Im Gegensatz zu Nicht-Aggregatklassen können sie mit geschweiften Klammern initialisiert werden {}
. Diese Initialisierungssyntax ist allgemein für Arrays bekannt, und wir haben gerade gelernt, dass diese Aggregate sind. Fangen wir also mit ihnen an.
Type array_name[n] = {a1, a2, …, am};
if(m == n)
das i th Element des Arrays wird mit einer i
sonst if(m < n)
werden die ersten m Elemente des Arrays mit einem 1 , a 2 , , a m und das andere n - m
Elemente sind, wenn möglich, Wert-initialisiert (siehe unten für die Erklärung des Begriffs)
else if(m > n)
wird der Compiler einen Fehler ausgeben
sonst (dies ist der Fall, wenn n überhaupt nicht angegeben ist, wie int a[] = {1, 2, 3};
)
die Größe des Feldes (n) wird als gleich m angenommen, so dass int a[] = {1, 2, 3};
ist gleichbedeutend mit int a[3] = {1, 2, 3};
Wenn ein Objekt vom skalaren Typ ( bool
, int
, char
, double
Zeiger, etc.) ist Wert-initialisiert bedeutet dies, dass es initialisiert wird mit 0
für diesen Typ ( false
para bool
, 0.0
para double
, usw.). Wenn ein Objekt vom Typ Klasse mit einem vom Benutzer deklarierten Standardkonstruktor wertinitialisiert wird, wird sein Standardkonstruktor aufgerufen. Wenn der Standardkonstruktor implizit definiert ist, werden alle nichtstatischen Mitglieder rekursiv wertinitialisiert. Diese Definition ist ungenau und ein wenig falsch, aber sie sollte Ihnen die grundlegende Idee 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 Initialisierung von Arrays:
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 auf die gleiche Weise. Anstelle der Array-Elemente werden wir die nicht-statischen Datenelemente in der Reihenfolge ihres Auftretens in der Klassendefinition initialisieren (sie sind alle per Definition öffentlich). Wenn es weniger Initialisierer als Mitglieder gibt, wird der Rest wertinitialisiert. Wenn es nicht möglich ist, eines der Mitglieder, die nicht explizit initialisiert wurden, wertmäßig zu initialisieren, erhalten wir einen Kompilierfehler. Wenn es mehr Initialisierer als nötig gibt, erhalten wir ebenfalls einen Kompilierfehler.
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 y.c
wird initialisiert mit 'a'
, y.x.i1
mit 10
, y.x.i2
mit 20
, y.i[0]
mit 20
, y.i[1]
mit 30
y y.f
ist wertinitialisiert, d.h. initialisiert mit 0.0
. Das geschützte statische Mitglied d
wird überhaupt nicht initialisiert, da es sich um static
.
Aggregate-Unions unterscheiden sich insofern, als dass nur ihr erstes Glied mit geschweiften Klammern initialisiert werden kann. Ich denke, wenn Sie in C++ fortgeschritten genug sind, um die Verwendung von Unions überhaupt in Betracht zu ziehen (ihre Verwendung kann sehr gefährlich sein und muss sorgfältig durchdacht werden), können Sie die Regeln für Unions im Standard selbst nachschlagen :).
Nachdem wir nun wissen, was das Besondere an Aggregaten ist, wollen wir versuchen, die Einschränkungen für Klassen zu verstehen, d. h. warum es sie gibt. Wir sollten verstehen, dass eine mitgliederweise Initialisierung mit geschweiften Klammern bedeutet, dass die Klasse nichts anderes ist als die Summe ihrer Mitglieder. Wenn ein benutzerdefinierter Konstruktor vorhanden ist, bedeutet dies, dass der Benutzer zusätzliche Arbeit leisten muss, um die Mitglieder zu initialisieren, so dass die Klammerinitialisierung nicht korrekt wäre. Wenn virtuelle Funktionen vorhanden sind, bedeutet dies, dass die Objekte dieser Klasse (bei den meisten Implementierungen) einen Zeiger auf die so genannte vtable der Klasse haben, die im Konstruktor gesetzt wird, so dass eine Klammerinitialisierung unzureichend wäre. Den Rest der Einschränkungen können Sie auf ähnliche Weise herausfinden (als Übung :).
Also genug von den Aggregaten. Jetzt können wir eine strengere Gruppe von Typen definieren, nämlich PODs
Formale Definition aus dem C++-Standard ( C++03 9 §4 ) :
Eine POD-Struktur ist eine aggregierte Klasse die keine nicht-statischen Datenelemente vom vom Typ nicht-POD-struct, nicht-POD-union (oder Array solcher Typen) oder Referenz hat, und keinen benutzerdefinierten Kopierzuweisungs Operator und keinen benutzerdefinierten Destruktor. Analog dazu ist eine POD-union eine aggregierte Vereinigung, die keine nicht-statische Datenmitglieder vom Typ nicht-POD-struct, nicht-POD-union (oder Array solcher Typen) oder Referenz hat, und keinen benutzerdefinierten Kopierzuweisungs Operator und keinen benutzerdefinierten Destruktor. Eine POD-Klasse ist eine Klasse die entweder ein POD-struct oder eine POD-Vereinigung ist.
Wow, das hier ist schon schwieriger zu analysieren, nicht wahr? :) Lassen wir die Gewerkschaften weg (aus denselben Gründen wie oben) und formulieren wir es etwas klarer:
Eine Aggregatklasse wird als POD bezeichnet, wenn sie keinen benutzerdefinierten Kopier-Zuweisungs Operator und Destruktor hat und keines ihrer ihrer nichtstatischen Mitglieder ist eine Nicht-POD Klasse, ein Array von Nicht-POD oder eine Referenz ist.
Was impliziert diese Definition? (Habe ich erwähnt POD steht für Normale Daten ?)
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-Einheiten, skalare Typen und Arrays solcher Typen werden zusammenfassend als POD-Typen.
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 diesen können PODs Mitgliedsfunktionen und beliebige statische Mitglieder haben, aber keines dieser beiden ändert das Speicherlayout des Objekts. Wenn Sie also eine mehr oder weniger portable dynamische Bibliothek schreiben wollen, die von C und sogar .NET aus verwendet werden kann, sollten Sie versuchen, alle Ihre exportierten Funktionen so zu gestalten, dass sie nur Parameter des POD-Typs annehmen und zurückgeben.
Die Lebensdauer von Objekten eines Nicht-POD-Klassentyps beginnt, wenn der Konstruktor beendet ist, und endet, wenn der Destruktor beendet ist. Bei POD-Klassen beginnt die Lebensdauer, wenn Speicherplatz für das Objekt belegt wird, und endet, wenn dieser Speicherplatz freigegeben oder wiederverwendet wird.
Für Objekte des POD-Typs ist durch den Standard gewährleistet, dass bei memcpy
den Inhalt Ihres Objekts in ein Array von char oder unsigned char, und dann memcpy
Wenn Sie den Inhalt wieder in Ihr Objekt übertragen, behält das Objekt seinen ursprünglichen Wert. Beachten Sie bitte, dass es für Objekte, die nicht vom Typ POD sind, keine solche Garantie gibt. Außerdem können Sie POD-Objekte sicher kopieren mit memcpy
. Im folgenden Beispiel wird angenommen, daß 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
goto-Anweisung. Wie Sie vielleicht wissen, ist es unzulässig (der Compiler sollte einen Fehler ausgeben), per goto von einem Punkt, an dem eine Variable noch nicht im Geltungsbereich war, zu einem Punkt zu springen, an dem sie bereits im Geltungsbereich ist. Diese Einschränkung gilt nur, wenn die Variable nicht vom Typ POD ist. Im folgenden Beispiel f()
schlecht geformt ist, während g()
wohlgeformt ist. Beachten Sie, dass Microsofts Compiler diese Regel zu großzügig handhabt - er gibt in beiden Fällen einfach eine Warnung aus.
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 Füllung vorhanden ist. Mit anderen Worten: Wenn das erste Element einer POD-Klasse A vom Typ T ist, können Sie sicher reinterpret_cast
de A*
a T*
und erhalten den Zeiger auf das erste Mitglied und umgekehrt.
Die Liste geht weiter und weiter
Es ist wichtig zu verstehen, was genau ein POD ist, da sich viele Sprachfunktionen, wie Sie sehen, bei ihnen anders verhalten.
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), keine Klammer-oder-Gleich-Initialisierer für nichtstatische Datenelemente (9.2), keine privaten oder geschützten nicht statische Datenmitglieder (Klausel 11), keine Basisklassen (Klausel 10) und keine virtuellen Funktionen (10.3).
Ok, was hat sich geändert?
Zuvor konnte ein Aggregat keine vom Benutzer deklariert Konstruktoren, aber jetzt kann es nicht mehr vom Benutzer bereitgestellte Konstrukteure. Gibt es einen Unterschied? Ja, es gibt einen, denn jetzt können Sie Konstruktoren und Standard sie:
struct Aggregate {
Aggregate() = default; // asks the compiler to generate the default implementation
};
Dies ist immer noch ein Aggregat, weil ein Konstruktor (oder eine spezielle Mitgliedsfunktion) die bei der ersten Deklaration voreingestellt ist ist nicht benutzerdefiniert.
Nun kann ein Aggregat keine Klammer-oder-Gleich-Initialisierer für nicht-statische Datenelemente. Was ist damit gemeint? Nun, das liegt einfach daran, dass wir mit diesem neuen Standard Mitglieder direkt in der Klasse initialisieren können, wie hier:
struct NotAggregate {
int x = 5; // valid in C++11
std::vector<int> s{1,2,3}; // also valid
};
Die Verwendung dieser Funktion macht die Klasse nicht mehr zu einem Aggregat, da sie im Grunde genommen gleichbedeutend mit der Bereitstellung eines eigenen Standardkonstruktors ist.
Was ein Aggregat ist, hat sich also kaum verändert. Es ist immer noch derselbe Grundgedanke, angepasst an die neuen Funktionen.
Bei den PODs gab es viele Veränderungen. In dieser neuen Norm wurden viele frühere Regeln für LEB gelockert und die Art und Weise, wie die Definition in der Norm gegeben wird, wurde grundlegend geändert.
Die Idee eines PODs besteht darin, im Wesentlichen zwei unterschiedliche Eigenschaften zu erfassen:
Aus diesem Grund wurde die Definition in zwei unterschiedliche Konzepte aufgeteilt: trivial Klassen und Standard-Layout Klassen, weil diese nützlicher sind als POD. In der Norm wird der Begriff POD nur noch selten verwendet, stattdessen wird der spezifischere Begriff trivial y Standard-Layout Konzepte.
Die neue Definition besagt im Wesentlichen, dass ein POD eine Klasse ist, die sowohl trivial ist als auch ein Standard-Layout hat, und dass diese Eigenschaft rekursiv für alle nicht-statischen Datenelemente gelten muss:
Eine POD struct ist eine Nicht-Union-Klasse, die sowohl eine triviale Cla und keine nichtstatischen Datenmitglieder vom Typ Nicht-POD struct, Nicht-POD union (oder Array solcher Typen) hat. Entsprechend ist eine POD-Union eine Union, die sowohl eine Trivialklasse als auch eine Standard-Layoutklasse ist, und die keine nichtstatischen Datenmitglieder vom Typ non-POD struct, non-POD union (oder Array solcher Typen). Eine POD-Klasse ist eine Klasse, die entweder eine POD-Struktur oder eine POD-Union ist.
Gehen wir nun auf jede dieser beiden Eigenschaften im Detail ein.
Triviales ist die erste oben genannte Eigenschaft: Trivialklassen unterstützen statische Initialisierung. Wenn eine Klasse trivial kopierbar ist (eine Obermenge trivialer Klassen), ist es in Ordnung, ihre Repräsentation überall hin zu kopieren mit Dingen wie memcpy
und erwarten, dass das Ergebnis dasselbe ist.
Der Standard definiert eine Trivialklasse wie folgt:
Eine trivial kopierbare Klasse ist eine Klasse, die:
- hat keine nicht-trivialen Kopierkonstruktoren (12.8),
- hat keine nicht-trivialen Zugkonstruktoren (12.8),
- hat keine nichttrivialen Kopierzuweisungsoperatoren (13.5.3, 12.8),
- hat keine nichttrivialen Zuweisungsoperatoren (13.5.3, 12.8), und
- hat einen trivialen Destruktor (12.4).
Eine Trivialklasse ist eine Klasse, die einen trivialen Standardkonstruktor (12.1) hat und trivial kopierbar ist.
[ Nota: Insbesondere hat eine trivial kopierbare oder triviale Klasse keine virtuellen Funktionen oder virtuelle Basisklassen. -Ende Anmerkung ]
Was sind nun all diese trivialen und nicht-trivialen Dinge?
Ein Kopier-/Verschiebekonstruktor für die Klasse X ist trivial, wenn er nicht vom Benutzer bereitgestellt wird und wenn
- die Klasse X hat keine virtuellen Funktionen (10.3) und keine virtuellen Basisklassen (10.1), und
- der Konstruktor, der zum Kopieren/Verschieben jedes direkten Unterobjekts der Basisklasse ausgewählt wurde, ist trivial, und
- für jedes nichtstatische Datenelement von X, das vom Typ Klasse (oder Array davon) ist, der Konstruktor der zum Kopieren/Verschieben dieses Elements ausgewählt wird, trivial;
sonst ist der Kopier-/Verschiebekonstruktor nicht trivial.
Im Grunde bedeutet dies, dass ein Kopier- oder Verschiebekonstruktor 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 trivialen Kopier-/Verschiebe-Zuweisungsoperators ist sehr ähnlich, wobei einfach das Wort "Konstruktor" durch "Zuweisungsoperator" ersetzt wird.
Ein trivialer Destruktor hat ebenfalls eine ähnliche Definition, mit der zusätzlichen Einschränkung, dass er nicht virtuell sein darf.
Und eine weitere ähnliche Regel gilt für triviale Standardkonstruktoren, mit dem Zusatz, dass ein Standardkonstruktor nicht-trivial ist, wenn die Klasse nicht-statische Datenelemente mit Klammer-oder-Gleich-Initialisierer , die wir oben gesehen haben.
Hier sind einige Beispiele, um alles zu verdeutlichen:
// empty classes are trivial
struct Trivial1 {};
// all special members are implicit
struct Trivial2 {
int x;
};
struct Trivial3 : Trivial2 { // base class is trivial
Trivial3() = default; // not a user-provided ctor
int y;
};
struct Trivial4 {
public:
int a;
private: // no restrictions on access modifiers
int b;
};
struct Trivial5 {
Trivial1 a;
Trivial2 b;
Trivial3 c;
Trivial4 d;
};
struct Trivial6 {
Trivial2 a[23];
};
struct Trivial7 {
Trivial6 c;
void f(); // it's okay to have non-virtual functions
};
struct Trivial8 {
int x;
static NonTrivial1 y; // no restrictions on static members
};
struct Trivial9 {
Trivial9() = default; // not user-provided
// a regular constructor is okay because we still have default ctor
Trivial9(int x) : x(x) {};
int x;
};
struct NonTrivial1 : Trivial3 {
virtual void f(); // virtual members make non-trivial ctors
};
struct NonTrivial2 {
NonTrivial2() : z(42) {} // user-provided ctor
int z;
};
struct NonTrivial3 {
NonTrivial3(); // user-provided ctor
int w;
};
NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
// still counts as user-provided
struct NonTrivial5 {
virtual ~NonTrivial5(); // virtual destructors are not trivial
};
Standard-Layout ist die zweite Eigenschaft. Der Standard erwähnt, dass diese für die Kommunikation mit anderen Sprachen nützlich sind, da eine Klasse mit Standard-Layout das gleiche Speicherlayout wie die entsprechende C-Struktur oder Union hat.
Dies ist eine weitere Eigenschaft, die rekursiv für Mitglieder und alle Basisklassen gelten muss. Und wie üblich sind keine virtuellen Funktionen oder virtuellen Basisklassen erlaubt. Das würde das Layout inkompatibel mit C machen.
Eine lockere Regel ist hier, dass Standard-Layout-Klassen alle nicht statischen Datenmitglieder mit der gleichen Zugriffskontrolle haben müssen. Zuvor mussten diese alle público aber jetzt können Sie sie privat oder geschützt machen, solange sie alle privat oder alle geschützt.
Bei Verwendung der Vererbung, nur einer Klasse im gesamten Vererbungsbaum kann nichtstatische Datenmitglieder haben, und das erste nichtstatische Datenmitglied kann nicht von einem Basisklassentyp sein (dies könnte die Aliasing-Regeln verletzen), andernfalls ist es keine Standard-Layout-Klasse.
So lautet die Definition im Standardtext:
Eine Standard-Layout-Klasse ist eine Klasse, die:
- hat keine nicht statischen Datenelemente vom Typ Nicht-Standard-Layoutklasse (oder Array oder Referenz,
- hat keine virtuellen Funktionen (10.3) und keine virtuellen Basisklassen (10.1),
- hat die gleiche Zugriffskontrolle (Klausel 11) für alle nicht-statischen Datenelemente,
- hat keine Nicht-Standard-Layout-Basisklassen,
- hat entweder keine nichtstatischen Datenelemente in der am meisten abgeleiteten Klasse und höchstens eine Basisklasse mit nichtstatischen Datenelementen oder hat keine Basisklassen mit nichtstatischen Datenelementen und
- keine Basisklassen desselben Typs wie das erste nichtstatische Datenelement hat.
Eine Standard-Layout-Struktur ist eine Standard-Layout-Klasse, die die Klasse class-key class.
Eine Standard-Layoutvereinigung ist eine Standard-Layoutklasse, die mit dem Klassenschlüssel union definiert ist.
[ Nota: Standard-Layout-Klassen sind nützlich für die Kommunikation mit Code, der in anderen Programmiersprachen geschrieben wurde. Ihr Layout ist in 9.2 beschrieben. -Ende Anmerkung ]
Dazu ein paar Beispiele.
// 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
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 einen der Typen Trivial oder Standard-Layout handelt).
Die Standardbibliothek verfügt über Traits zum Testen dieser Eigenschaften in der Kopfzeile <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;
Wir können uns auf die Entwurf des C++14-Standards als Referenz.
Dies wird in Abschnitt 8.5.1
Aggregate was uns die folgende Definition liefert:
Ein Aggregat ist ein Array oder eine Klasse (Klausel 9) ohne vom Benutzer bereitgestellte Konstruktoren (12.1), keine privaten oder geschützten nicht-statischen Datenelemente (Klausel 11), keine Basisklassen (Klausel 10) und keine virtuellen Funktionen (10.3).
Die einzige Änderung besteht darin, dass nun Folgendes hinzugefügt wird klasseninterne Mitgliedsinitialisierungen macht eine Klasse nicht zu einem Nicht-Aggregat. So ist das folgende Beispiel aus C++11-Aggregat-Initialisierung für Klassen mit In-Place-Initialisierungen von Mitgliedern :
struct A
{
int a = 3;
int b = 3;
};
war in C++11 kein Aggregat, ist es aber in C++14. Diese Änderung wird behandelt in N3605: Mitgliederinitialisierungen und Aggregate mit der folgenden Zusammenfassung:
Bjarne Stroustrup und Richard Smith haben ein Problem mit Aggregaten angesprochen Initialisierung und Member-Initialisierer nicht zusammen funktionieren. Dieses Papier schlägt vor, das Problem zu beheben, indem es den von Smith vorgeschlagenen Wortlaut annimmt der die Einschränkung aufhebt, dass Aggregate nicht über Mitglied-Initialisierer haben können.
Die Definition für POD( einfache alte Daten ) Struktur wird im Abschnitt 9
Klassen die besagt:
Eine POD-Struktur 110 ist eine Nichtvereinigungsklasse, die sowohl eine Trivialklasse als auch eine Klasse mit Standard-Layout ist und keine nicht statischen Datenelemente vom Typ nicht-POD struct, nicht-POD union (oder Array solcher Typen). In ähnlicher Weise ist eine POD-Union ist eine Union, die sowohl eine Trivialklasse als auch eine Standard-Layout-Klasse ist und keine nichtstatischen Datenmitglieder vom Typ nicht-POD struct, nicht-POD union (oder Array solcher Typen). Eine POD-Klasse ist eine Klasse, die entweder ein POD struct oder eine POD union ist.
was der gleichen Formulierung wie in C++11 entspricht.
Wie in den Kommentaren erwähnt pod stützt sich auf die Definition von Standard-Layout und das hat sich für C++14 geändert, aber das geschah über Fehlerberichte, die nachträglich auf C++14 angewendet wurden.
Es gab drei DRs:
Also Standard-Layout ging von diesem Pre C++14:
Eine Standard-Layout-Klasse ist eine Klasse, die:
- (7.1) hat keine nichtstatischen 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 die gleiche Zugriffskontrolle (Klausel [class.access]) für alle nichtstatischen Datenmitglieder,
- (7.4) hat keine Nicht-Standard-Layout-Basisklassen,
- (7.5) hat entweder keine nichtstatischen Datenelemente in der am meisten abgeleiteten Klasse und höchstens eine Basisklasse mit nichtstatischen Datenelementen, oder hat keine Basisklassen mit nichtstatischen Datenelementen und
- (7.6) hat keine Basisklassen vom gleichen Typ wie das erste nichtstatische Datenelement.109
An dies in C++14 :
Eine Klasse S ist eine Standard-Layout-Klasse, wenn sie:
- (3.1) hat keine nichtstatischen 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 Unterobjekt einer Basisklasse eines bestimmten Typs,
- (3.6) alle nicht statischen Datenelemente und Bit-Felder in der Klasse und ihren Basisklassen zunächst in derselben Klasse deklariert werden, und
- (3.7) hat kein Element der Menge M(S) der Typen als Basisklasse, wobei für jeden Typ X M(X) wie folgt definiert ist.104 [Anmerkung: M(X) ist die Menge der Typen aller nicht zur Basisklasse gehörenden Unterobjekte, die in X einen Null-Offset haben können. -Ende Anmerkung ]
- (3.7.1) Wenn X ein Nicht-Union-Klassentyp ohne (möglicherweise vererbte) nichtstatische Datenmitglieder ist, ist die Menge M(X) leer.
- (3.7.2) Wenn X ein Nicht-Union-Klassentyp mit einem nichtstatischen Datenelement vom Typ X0 ist, das entweder die Größe Null hat oder das erste nicht-statisches Datenmitglied von X (wobei dieses Mitglied eine anonyme union), 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 der Menge, die alle Ui enthält, 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.
Download des endgültigen Entwurfs des internationalen Standards C++17 aquí .
Aggregate
C++17 erweitert und verbessert die Aggregate und die Aggregatinitialisierung. Die Standardbibliothek enthält jetzt auch eine std::is_aggregate
Typ Eigenschaftsklasse. Hier ist die formale Definition aus den Abschnitten 11.6.1.1 und 11.6.1.2 (interne Verweise weggelassen):
Ein Aggregat ist ein Array oder eine Klasse mit
- keine vom Benutzer bereitgestellten, expliziten oder vererbten Konstruktoren,
- keine privaten oder geschützten nicht-statischen Datenelemente,
- keine virtuellen Funktionen, und
- keine virtuellen, privaten oder geschützten Basisklassen.
[ Hinweis: Die Aggregat-Initialisierung erlaubt keinen Zugriff auf geschützte und private Mitglieder oder Konstruktoren der Basisklasse. -Ende Hinweis ]
Die Elemente eines Aggregats sind:
- für ein Array, die Arrayelemente in aufsteigender tiefgestellter Reihenfolge, oder
- für eine Klasse die direkten Basisklassen in der Reihenfolge ihrer Deklaration, gefolgt von den direkten nicht statischen Datenmitgliedern, die nicht Mitglieder einer anonymen Vereinigung sind, in der Reihenfolge ihrer Deklaration.
Was hat sich geändert?
Aggregate können jetzt öffentliche, nicht-virtuelle Basisklassen haben. Außerdem ist es nicht erforderlich, dass Basisklassen Aggregate sind. Wenn sie keine Aggregate sind, 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
Explizit vorgegebene Konstruktoren sind nicht zulässig
struct D // not an aggregate { int i = 0; D() = default; explicit D(D const&) = default; };
Vererbende Konstruktoren sind unzulässig
struct B1 { int i1; B1() : i1(0) { } }; struct C : B1 // not an aggregate { using B1::B1; };
Trivial-Klassen
Die Definition der trivialen Klasse 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 Verweise wurden gestrichen):
Eine trivial kopierbare Klasse ist eine Klasse:
- wobei jeder Kopierkonstruktor, jeder Verschiebekonstruktor, jeder Kopierzuweisungsoperator und jeder Verschiebezuweisungsoperator entweder gelöscht oder trivial ist,
- die mindestens einen nicht gelöschten Kopierkonstruktor, Verschiebekonstruktor, Kopierzuweisungsoperator oder Verschiebezuweisungsoperator hat und
- die einen trivialen, nicht gelöschten Destruktor hat.
Eine Trivialklasse ist eine Klasse, die trivial kopierbar ist und einen oder mehrere Standardkonstruktoren hat, von denen alle entweder trivial oder gelöscht sind und von denen mindestens einer nicht gelöscht wird. [Hinweis: Insbesondere ist eine trivial kopierbare oder triviale Klasse hat weder virtuelle Funktionen noch virtuelle Basisklassen. end note ]
Änderungen:
std::memcpy
. Dies war ein semantischer Widerspruch, denn durch die Definition aller Konstruktor-/Zuweisungsoperatoren als gelöscht beabsichtigte der Ersteller der Klasse eindeutig, dass die Klasse nicht kopiert/verschoben werden kann, obwohl die Klasse immer noch die Definition einer trivial kopierbaren Klasse erfüllte. Daher gibt es in C++17 eine neue Klausel, die besagt, dass trivial kopierbare Klassen mindestens einen trivialen, nicht gelöschten (wenn auch nicht notwendigerweise öffentlich zugänglichen) Kopier-/Verschiebe-Konstruktor/Zuweisungsoperator haben müssen. Siehe N4148 , DR1734Standard-Layout-Klassen
Die Definition von Standard-Layout wurde ebenfalls überarbeitet, um Fehlerberichte zu berücksichtigen. Auch hier waren die Änderungen technischer Natur. Hier ist der Text aus der Norm (12.0.7). Wie zuvor wurden interne Verweise ausgelassen:
Eine Klasse S ist eine Standard-Layout-Klasse, wenn sie:
- hat keine nichtstatischen Datenelemente vom Typ Nicht-Standard-Layoutklasse (oder Array solcher Typen) oder Referenz,
- hat keine virtuellen Funktionen und keine virtuellen Basisklassen,
- hat die gleiche Zugriffskontrolle für alle nicht-statischen Datenelemente,
- hat keine Nicht-Standard-Layout-Basisklassen,
- hat höchstens ein Unterobjekt einer Basisklasse eines bestimmten Typs,
- alle nicht statischen Datenelemente und Bit-Felder in der Klasse und ihren Basisklassen zunächst in derselben Klasse deklariert werden, 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-Union-Klassentyp ohne (möglicherweise vererbte) nichtstatische Datenmitglieder ist, ist die Menge M(X) leer.
- Wenn X ein Nicht-Union-Klassentyp ist, dessen erstes nicht-statisches Datenelement vom Typ X0 ist (wobei dieses Element eine anonyme Union 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 der Menge, die alle Ui enthält, 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).
- Ist X ein Nicht-Klassen-, Nicht-Array-Typ, ist die Menge M(X) leer.
[Anmerkung: M(X) ist die Menge der Typen aller Unterobjekte, die nicht der Basisklasse angehören und die in einer Klasse mit Standard-Layout garantiert auf einem Null-Offset in X liegen.]
[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) Damit wird sichergestellt, dass zwei Unterobjekte, die denselben Klassentyp haben und zum selben am meisten abgeleiteten Objekt gehören, nicht an derselben Adresse zugewiesen werden.
Änderungen:
Nota: Das C++-Standardisierungskomitee beabsichtigte, die oben genannten Änderungen, die auf Fehlerberichten beruhen, auf C++14 anzuwenden, obwohl die neue Sprache nicht im veröffentlichten C++14-Standard enthalten ist. Sie ist im C++17-Standard enthalten.
Wie das übrige Thema dieser Frage zeigt, ändert sich die Bedeutung und Verwendung von Aggregaten mit jeder Norm. Am Horizont zeichnen sich mehrere wichtige Änderungen ab.
In C++17 ist dieser Typ immer noch ein Aggregat:
struct X {
X() = delete;
};
Und daher, X{}
kompiliert trotzdem, da es sich um eine Aggregatinitialisierung und nicht um einen Konstruktoraufruf handelt. Siehe auch: Wann ist ein privater Konstruktor kein privater Konstruktor?
In C++20 wird die Einschränkung von "require" auf "require" geändert:
keine benutzerdefinierten,
explicit
oder geerbte Konstruktoren
zu
keine vom Benutzer deklarierten oder vererbten Konstruktoren
Dies wurde in die C++20 Arbeitsentwurf . Weder die X
hier noch die C
in der verlinkten Frage werden Aggregate in C++20 sein.
Dies führt auch zu einem Jo-Jo-Effekt bei dem folgenden Beispiel:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
In C++11/14, B
fue no ein Aggregat aufgrund der Basisklasse, also B{}
führt eine Wertinitialisierung durch, die die B::B()
die aufruft A::A()
an einer Stelle, an der sie zugänglich ist. Dies war wohlgeformt.
In C++17, B
wurde zu einem Aggregat, weil Basisklassen erlaubt waren, was die B{}
Aggregat-Initialisierung. Dies erfordert die Copy-List-Initialisierung eines A
de {}
aber außerhalb des Kontextes von B
wo sie nicht zugänglich ist. In C++17 ist dies schlecht geformt ( auto x = B();
wäre aber in Ordnung).
In C++20 jetzt, wegen der oben genannten Regeländerung, B
ist wiederum kein Aggregat mehr (nicht wegen der Basisklasse, sondern wegen des vom Benutzer deklarierten Standardkonstruktors - auch wenn dieser standardmäßig verwendet wird). Wir sind also wieder dabei, durch B
Konstruktor, und dieser Ausschnitt wird wohlgeformt.
Ein häufig auftretendes Problem ist der Wunsch nach der Verwendung von emplace()
-Style Konstruktoren mit Aggregaten:
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
Dies funktioniert nicht, weil emplace
wird versuchen, die Initialisierung effektiv durchzuführen X(1, 2)
die nicht gültig ist. Die typische Lösung ist das Hinzufügen eines Konstruktors zu X
aber mit diesem Vorschlag (der derzeit den Weg durch Core findet), werden Aggregate effektiv über synthetische Konstruktoren verfügen, die das Richtige tun - und sich wie reguläre Konstruktoren verhalten. Der obige Code wird in C++20 unverändert kompiliert.
In C++17 lässt sich dies nicht kompilieren:
template <typename T>
struct Point {
T x, y;
};
Point p{1, 2}; // error
Die Benutzer müssten für alle Aggregatvorlagen einen eigenen Abzugsleitfaden erstellen:
template <typename T> Point(T, T) -> Point<T>;
Da dies aber in gewisser Weise "das Offensichtliche" ist und es sich im Grunde nur um eine Standardformulierung handelt, wird die Sprache dies für Sie tun. Dieses Beispiel wird in C++20 kompiliert (ohne die Notwendigkeit der vom Benutzer bereitgestellten Ableitungsanleitung).
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.
1 Stimmen
Was ist eine POD-Teilmenge: stackoverflow.com/questions/146452/was-sind-pod-types-in-c
0 Stimmen
Kann man sagen, dass die Motivation hinter diesen Definitionen ungefähr ist: POD == memcpy'able, Aggregate == aggregate-initializable?