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?
Diese FAQ befasst sich mit Aggregaten und PODs und behandelt das folgende Material:
Antworten:
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:
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 ++ class
bezieht 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 - m
Elementeninitialisiertwerden 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 0
für diesen Typen ( false
für bool
, 0.0
fü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.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
und y.f
ist Wert initialisiert, die mit wird, initialisiert 0.0
. Das geschützte statische Element d
wird ü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
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 ?)
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 memcpy
behält, wenn Sie den Inhalt Ihres Objekts in ein Array von Zeichen oder Zeichen ohne Vorzeichen und dann memcpy
den 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_cast
von A*
zu T*
und den Zeiger auf das erste Element erhalten 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, für sie unterschiedlich verhalten.
private:
ggf. einfügen ): struct A { int const a; };
dann A()
ist wohlgeformt, auch wenn A
die Standardkonstruktordefinition schlecht geformt wäre.
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?
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.
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.
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:
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.
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 memcpy
und 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
};
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
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;
Wir können uns auf den Draft C ++ 14-Standard als Referenz beziehen.
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.
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.
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.
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
, private
oder protected
. Sie können einige public
und 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 private
trotzdem 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, Derived
muss diese Klasse sein (da sie Base
ein Mitglied hat). So Base
muss leer sein (von Daten). Und wenn Base
ist leer, sowie eine Basisklasse ... warum überhaupt ein Datenmitglied?
Da Base
es leer ist, hat es keinen Zustand. Alle nicht statischen Elementfunktionen tun also das, was sie tun, basierend auf ihren Parametern und nicht auf ihrem this
Zeiger.
Also nochmal: kein großer Verlust.
static_cast<Base*>(&d)
und &d.b
vom gleichen Base*
Typ, weisen sie auf verschiedene Dinge hin und verstoßen so gegen die Aliasing-Regel. Bitte korrigieren Sie mich.
Derived
muss diese Klasse sein?
Derived
das 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.
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_aggregate
Typmerkmalsklasse. 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?
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
struct D // not an aggregate
{
int i = 0;
D() = default;
explicit D(D const&) = default;
};
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:
std::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 , DR1734Standard-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:
—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
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:
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.
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.
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
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 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, B
war 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 B
zu einem Aggregat, da Basisklassen zulässig waren, die eine B{}
Aggregatinitialisierung durchführten. Dies erfordert eine Initialisierung der Kopierliste A
von {}
, 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 B
wieder 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 B
Konstruktor, und dieses Snippet wird wohlgeformt.
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 emplace
versucht 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.
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).