Questa FAQ riguarda aggregati e POD e copre il seguente materiale:
- Cosa sono gli aggregati ?
- Cosa sono i POD (Plain Old Data)?
- Come sono collegati?
- Come e perché sono speciali?
- Cosa cambia per C ++ 11?
Questa FAQ riguarda aggregati e POD e copre il seguente materiale:
Risposte:
Questo articolo è piuttosto lungo Se vuoi conoscere sia gli aggregati che i POD (Plain Old Data), prenditi del tempo e leggilo. Se sei interessato solo agli aggregati, leggi solo la prima parte. Se si è interessati solo in POD allora è necessario prima leggere la definizione, implicazioni, ed esempi di aggregati e poi si può passare al POD, ma io consiglierei comunque leggere la prima parte nella sua interezza. La nozione di aggregati è essenziale per la definizione dei POD. Se trovi errori (anche minori, inclusi grammatica, stile, formattazione, sintassi, ecc.), Lascia un commento, lo modificherò.
Questa risposta si applica a C ++ 03. Per altri standard C ++ vedere:
Definizione formale dallo standard C ++ ( C ++ 03 8.5.1 §1 ) :
Un aggregato è un array o una classe (clausola 9) senza costruttori dichiarati dall'utente (12.1), senza membri di dati non statici privati o protetti (clausola 11), senza classi di base (clausola 10) e senza funzioni virtuali (10.3 ).
Quindi, OK, analizziamo questa definizione. Prima di tutto, qualsiasi array è un aggregato. Una classe può anche essere un aggregato se ... aspetta! non si dice nulla su strutture o sindacati, non possono essere aggregati? Si Loro possono. In C ++, il termine si class
riferisce a tutte le classi, strutture e sindacati. Quindi, una classe (o struttura o unione) è un aggregato se e solo se soddisfa i criteri delle definizioni precedenti. Cosa implicano questi criteri?
Ciò non significa che una classe aggregata non possa avere costruttori, in realtà può avere un costruttore predefinito e / o un costruttore di copie purché siano implicitamente dichiarati dal compilatore e non esplicitamente dall'utente
Nessun membro di dati non statici privato o protetto . Puoi avere tutte le funzioni membro private e protette (ma non i costruttori), nonché quanti membri dati statici privati o protetti e funzioni membro desideri e non violare le regole per le classi aggregate
Una classe aggregata può avere un operatore e / o un distruttore di copia-assegnazione dichiarati dall'utente / definiti dall'utente
Un array è un aggregato anche se è un array di tipo di classe non aggregato.
Ora diamo un'occhiata ad alcuni esempi:
class NotAggregate1
{
virtual void f() {} //remember? no virtual functions
};
class NotAggregate2
{
int x; //x is private by default and non-static
};
class NotAggregate3
{
public:
NotAggregate3(int) {} //oops, user-defined constructor
};
class Aggregate1
{
public:
NotAggregate1 member1; //ok, public member
Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment
private:
void f() {} // ok, just a private function
};
Ti viene l'idea. Ora vediamo come gli aggregati sono speciali. A differenza delle classi non aggregate, possono essere inizializzate con parentesi graffe {}
. Questa sintassi di inizializzazione è comunemente nota per le matrici e abbiamo appena appreso che si tratta di aggregati. Quindi, cominciamo con loro.
Type array_name[n] = {a1, a2, …, am};
if (m == n)
l'i esimo elemento della matrice viene inizializzato con i
else if (m <n)
i primi m elementi dell'array vengono inizializzati con un 1 , un 2 , ..., a m e gli altrin - m
elementi sono, se possibile, inizializzati dal valore (vedi sotto per la spiegazione del termine)
altrimenti se (m> n)
il compilatore genererà un
altro errore (questo è il caso in cui n non è specificato affatto int a[] = {1, 2, 3};
)
la dimensione di si presume che l'array (n) sia uguale a m, quindiint a[] = {1, 2, 3};
è equivalente aint a[3] = {1, 2, 3};
Quando un oggetto di tipo scalare ( bool
, int
, char
, double
, puntatori, ecc) è valore inizializzato significa viene inizializzato con 0
per quel tipo ( false
per bool
, 0.0
per double
, ecc). Quando un oggetto di tipo classe con un costruttore predefinito dichiarato dall'utente viene inizializzato con valore, viene chiamato il suo costruttore predefinito. Se il costruttore predefinito viene definito implicitamente, tutti i membri non statici vengono inizializzati in modo ricorsivo. Questa definizione è imprecisa e un po 'errata, ma dovrebbe darti l'idea di base. Un riferimento non può essere inizializzato dal valore. L'inizializzazione del valore per una classe non aggregata può non riuscire se, ad esempio, la classe non ha un costruttore predefinito appropriato.
Esempi di inizializzazione di array:
class A
{
public:
A(int) {} //no default constructor
};
class B
{
public:
B() {} //default constructor available
};
int main()
{
A a1[3] = {A(2), A(1), A(14)}; //OK n == m
A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
int Array1[1000] = {0}; //All elements are initialized with 0;
int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
//the elements in this case are not value-initialized, but have indeterminate values
//(unless, of course, Array4 is a global array)
int array[2] = {1, 2, 3, 4}; //ERROR, too many initializers
}
Ora vediamo come le classi aggregate possono essere inizializzate con le parentesi graffe. Praticamente allo stesso modo. Invece degli elementi dell'array, inizializzeremo i membri di dati non statici nell'ordine del loro aspetto nella definizione della classe (sono tutti pubblici per definizione). Se ci sono meno inizializzatori rispetto ai membri, il resto viene inizializzato dal valore. Se è impossibile inizializzare il valore di uno dei membri che non sono stati inizializzati esplicitamente, viene visualizzato un errore di compilazione. Se sono presenti più inizializzatori del necessario, viene visualizzato anche un errore di compilazione.
struct X
{
int i1;
int i2;
};
struct Y
{
char c;
X x;
int i[2];
float f;
protected:
static double d;
private:
void g(){}
};
Y y = {'a', {10, 20}, {20, 30}};
Nell'esempio sopra y.c
è inizializzato con 'a'
, y.x.i1
con 10
, y.x.i2
con 20
, y.i[0]
con 20
, y.i[1]
con 30
ed y.f
è inizializzato valore, ovvero inizializzato con 0.0
. Il membro statico protetto d
non è affatto inizializzato, perché lo è static
.
I sindacati aggregati sono diversi in quanto è possibile inizializzare solo il loro primo membro con parentesi graffe. Penso che se sei abbastanza avanzato in C ++ per prendere in considerazione l'uso dei sindacati (il loro uso può essere molto pericoloso e deve essere considerato attentamente), potresti cercare tu stesso le regole per i sindacati :).
Ora che sappiamo cosa c'è di speciale negli aggregati, proviamo a capire le restrizioni sulle classi; cioè, perché sono lì. Dovremmo capire che l'inizializzazione a livello di membro con parentesi graffe implica che la classe non è altro che la somma dei suoi membri. Se è presente un costruttore definito dall'utente, significa che l'utente deve fare un lavoro extra per inizializzare i membri, pertanto l'inizializzazione del controvento sarebbe errata. Se sono presenti funzioni virtuali, significa che gli oggetti di questa classe hanno (sulla maggior parte delle implementazioni) un puntatore alla cosiddetta vtable della classe, che è impostata nel costruttore, quindi l'inizializzazione del controvento sarebbe insufficiente. Potresti capire il resto delle restrizioni in modo simile a un esercizio :).
Quindi abbastanza sugli aggregati. Ora possiamo definire un insieme più rigoroso di tipi, vale a dire, POD
Definizione formale dallo standard C ++ ( C ++ 03 9 §4 ) :
Una POD-struct è una classe aggregata che non ha membri di dati non statici di tipo non-POD-struct, non-POD-union (o array di tali tipi) o riferimenti, e non ha un operatore di assegnazione della copia definito dall'utente e nessun distruttore definito dall'utente. Allo stesso modo, un'unione POD è un'unione aggregata che non ha membri di dati non statici di tipo non-POD-struct, non-POD-union (o array di tali tipi) o riferimento e non ha un operatore di assegnazione della copia definito dall'utente e nessun distruttore definito dall'utente. Una classe POD è una classe che è una struttura POD o un'unione POD.
Wow, questo è più difficile da analizzare, no? :) Lasciamo fuori i sindacati (per gli stessi motivi di cui sopra) e riformuliamo in modo un po 'più chiaro:
Una classe aggregata viene chiamata POD se non ha un operatore e distruttore di assegnazione copia definiti dall'utente e nessuno dei suoi membri non statici è una classe non POD, un array di non POD o un riferimento.
Cosa implica questa definizione? (Ho già detto che POD sta per Plain Old Data ?)
Esempi:
struct POD
{
int x;
char y;
void f() {} //no harm if there's a function
static std::vector<char> v; //static members do not matter
};
struct AggregateButNotPOD1
{
int x;
~AggregateButNotPOD1() {} //user-defined destructor
};
struct AggregateButNotPOD2
{
AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};
Le classi POD, i sindacati POD, i tipi scalari e le matrici di tali tipi sono collettivamente chiamati tipi POD.
I POD sono speciali in molti modi. Fornirò solo alcuni esempi.
Le classi POD sono le più vicine alle strutture C. A differenza di loro, i POD possono avere funzioni membro e membri statici arbitrari, ma nessuno di questi due modifica il layout di memoria dell'oggetto. Quindi, se si desidera scrivere una libreria dinamica più o meno portatile che può essere utilizzata da C e persino .NET, si dovrebbe provare a fare in modo che tutte le funzioni esportate prendano e restituiscano solo parametri di tipo POD.
La durata degli oggetti di tipo di classe non POD inizia al termine del costruttore e termina al termine del distruttore. Per le classi POD, la durata inizia quando la memoria per l'oggetto è occupata e termina quando la memoria viene rilasciata o riutilizzata.
Per gli oggetti di tipo POD è garantito dallo standard che quando memcpy
il contenuto dell'oggetto viene inserito in un array di caratteri char o unsigned char, e quindi memcpy
il contenuto torna nell'oggetto, l'oggetto mantiene il suo valore originale. Si noti che non esiste tale garanzia per oggetti di tipo non POD. Inoltre, puoi tranquillamente copiare oggetti POD con memcpy
. L'esempio seguente presuppone che T sia un tipo POD:
#define N sizeof(T)
char buf[N];
T obj; // obj initialized to its original value
memcpy(buf, &obj, N); // between these two calls to memcpy,
// obj might be modified
memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
// holds its original value
vai alla dichiarazione. Come forse saprai, è illegale (il compilatore dovrebbe generare un errore) fare un salto tramite goto da un punto in cui alcune variabili non erano ancora nell'ambito fino a un punto in cui è già nell'ambito. Questa restrizione si applica solo se la variabile è di tipo non POD. Nel seguente esempio f()
è mal formato mentre g()
è ben formato. Si noti che il compilatore di Microsoft è troppo liberale con questa regola: emette solo un avviso in entrambi i casi.
int f()
{
struct NonPOD {NonPOD() {}};
goto label;
NonPOD x;
label:
return 0;
}
int g()
{
struct POD {int i; char c;};
goto label;
POD x;
label:
return 0;
}
È garantito che non ci sarà imbottitura all'inizio di un oggetto POD. In altre parole, se il primo membro di una classe POD A è di tipo T, puoi tranquillamente andare reinterpret_cast
da e ottenere il puntatore al primo membro e viceversa.A*
T*
La lista potrebbe continuare all'infinito…
È importante capire cosa sia esattamente un POD perché molte funzionalità del linguaggio, come vedi, si comportano diversamente per loro.
private:
come appropriato): struct A { int const a; };
allora A()
è ben formato, anche se A
la definizione predefinita del costruttore sarebbe mal formata.
La definizione standard di un aggregato è leggermente cambiata, ma è ancora praticamente la stessa:
Un aggregato è un array o una classe (clausola 9) senza costruttori forniti dall'utente (12.1), senza inizializzatori di parentesi graffa o uguale per membri di dati non statici (9.2), senza membri di dati non statici privati o protetti ( Clausola 11), nessuna classe di base (clausola 10) e nessuna funzione virtuale (10.3).
Ok, cosa è cambiato?
In precedenza, un aggregato non poteva avere costruttori dichiarati dall'utente , ma ora non può avere costruttori forniti dall'utente . C'è una differenza? Sì, c'è, perché ora puoi dichiarare i costruttori e predefinirli :
struct Aggregate {
Aggregate() = default; // asks the compiler to generate the default implementation
};
Questo è ancora un aggregato perché un costruttore (o qualsiasi funzione di membro speciale) che è predefinito sulla prima dichiarazione non è fornito dall'utente.
Ora un aggregato non può avere inizializzatori di parentesi graffa o uguale per membri di dati non statici. Cosa significa questo? Bene, questo è solo perché con questo nuovo standard, possiamo inizializzare i membri direttamente nella classe in questo modo:
struct NotAggregate {
int x = 5; // valid in C++11
std::vector<int> s{1,2,3}; // also valid
};
L'uso di questa funzione rende la classe non più un aggregato perché sostanzialmente equivale a fornire il proprio costruttore predefinito.
Quindi, ciò che è un aggregato non è cambiato molto. È sempre la stessa idea di base, adattata alle nuove funzionalità.
I POD hanno subito molti cambiamenti. Molte regole precedenti sui POD sono state ridotte in questo nuovo standard e il modo in cui la definizione è fornita nello standard è stato radicalmente modificato.
L'idea di un POD è di acquisire sostanzialmente due proprietà distinte:
Per questo motivo , la definizione è stata suddivisa in due concetti distinti: classi banali e classi di layout standard , perché sono più utili di POD. Lo standard ora usa raramente il termine POD, preferendo i concetti più banali e di layout standard più specifici .
La nuova definizione dice sostanzialmente che un POD è una classe che è al contempo banale e ha un layout standard e questa proprietà deve essere ricorsiva per tutti i membri di dati non statici:
Una struttura POD è una classe non sindacale che è sia una classe banale che una classe di layout standard e non ha membri di dati non statici di tipo struct non-POD, unione non POD (o array di tali tipi). Allo stesso modo, un'unione POD è un'unione che è sia una classe banale che una classe di layout standard e non ha membri di dati non statici di tipo non-POD struct, unione non-POD (o array di tali tipi). Una classe POD è una classe che è una struttura POD o un'unione POD.
Esaminiamo ciascuna di queste due proprietà in dettaglio separatamente.
Trivial è la prima proprietà di cui sopra: classi banali supportano l'inizializzazione statica. Se una classe è banalmente copiabile (un superset di classi banali), è ok copiare la sua rappresentazione sul posto con cose simili memcpy
e aspettarsi che il risultato sia lo stesso.
Lo standard definisce una classe banale come segue:
Una classe banalmente copiabile è una classe che:
- non ha costruttori di copie non banali (12.8),
- non ha costruttori di mosse non banali (12.8),
- non ha operatori di assegnazione di copie non banali (13.5.3, 12.8),
- non ha operatori di assegnazione di mosse non banali (13.5.3, 12.8) e
- ha un banale distruttore (12.4).
Una banale classe è una classe che ha un banale costruttore predefinito (12.1) ed è banalmente copiabile.
[ Nota: in particolare, una classe banalmente copiabile o banale non ha funzioni virtuali o classi di base virtuali. —Endola nota ]
Quindi, quali sono tutte quelle cose banali e non banali?
Un costruttore di copia / spostamento per la classe X è banale se non è fornito dall'utente e se
- la classe X non ha funzioni virtuali (10.3) e nessuna classe di base virtuale (10.1) e
- il costruttore selezionato per copiare / spostare ogni oggetto secondario della classe base diretta è banale, e
- per ogni membro di dati non statici di X di tipo di classe (o matrice di esso), il costruttore selezionato per copiare / spostare quel membro è banale;
altrimenti il costruttore copia / sposta non è banale.
Fondamentalmente questo significa che un costruttore di copia o spostamento è banale se non è fornito dall'utente, la classe non ha nulla di virtuale in essa e questa proprietà vale ricorsivamente per tutti i membri della classe e per la classe base.
La definizione di un banale operatore di assegnazione di copie / mosse è molto simile, sostituendo semplicemente la parola "costruttore" con "operatore di assegnazione".
Un banale distruttore ha anche una definizione simile, con il vincolo aggiunto che non può essere virtuale.
E ancora esiste un'altra regola simile per i costruttori di default banali, con l'aggiunta che un costruttore di default non è banale se la classe ha membri di dati non statici con inizializzatori di parentesi graffa o uguale , che abbiamo visto sopra.
Ecco alcuni esempi per chiarire tutto:
// empty classes are trivial
struct Trivial1 {};
// all special members are implicit
struct Trivial2 {
int x;
};
struct Trivial3 : Trivial2 { // base class is trivial
Trivial3() = default; // not a user-provided ctor
int y;
};
struct Trivial4 {
public:
int a;
private: // no restrictions on access modifiers
int b;
};
struct Trivial5 {
Trivial1 a;
Trivial2 b;
Trivial3 c;
Trivial4 d;
};
struct Trivial6 {
Trivial2 a[23];
};
struct Trivial7 {
Trivial6 c;
void f(); // it's okay to have non-virtual functions
};
struct Trivial8 {
int x;
static NonTrivial1 y; // no restrictions on static members
};
struct Trivial9 {
Trivial9() = default; // not user-provided
// a regular constructor is okay because we still have default ctor
Trivial9(int x) : x(x) {};
int x;
};
struct NonTrivial1 : Trivial3 {
virtual void f(); // virtual members make non-trivial ctors
};
struct NonTrivial2 {
NonTrivial2() : z(42) {} // user-provided ctor
int z;
};
struct NonTrivial3 {
NonTrivial3(); // user-provided ctor
int w;
};
NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
// still counts as user-provided
struct NonTrivial5 {
virtual ~NonTrivial5(); // virtual destructors are not trivial
};
Il layout standard è la seconda proprietà. Lo standard menziona che questi sono utili per comunicare con altre lingue, e questo perché una classe di layout standard ha lo stesso layout di memoria della struttura o unione C equivalente.
Questa è un'altra proprietà che deve essere ricorsiva per i membri e tutte le classi di base. E come al solito, non sono consentite funzioni virtuali o classi di base virtuali. Ciò renderebbe il layout incompatibile con C.
Una regola semplificata qui è che le classi di layout standard devono avere tutti i membri di dati non statici con lo stesso controllo di accesso. In precedenza questi dovevano essere tutti pubblici , ma ora puoi renderli privati o protetti, purché siano tutti privati o tutti protetti.
Quando si utilizza l'ereditarietà, solo una classe nell'intero albero dell'ereditarietà può avere membri di dati non statici e il primo membro di dati non statici non può essere di un tipo di classe di base (ciò potrebbe violare le regole di aliasing), altrimenti non è uno standard- classe di layout.
Ecco come va la definizione nel testo standard:
Una classe di layout standard è una classe che:
- non ha membri di dati non statici di tipo classe di layout non standard (o array di tali tipi) o riferimenti,
- non ha funzioni virtuali (10.3) e nessuna classe di base virtuale (10.1),
- ha lo stesso controllo di accesso (clausola 11) per tutti i membri di dati non statici,
- non ha classi base di layout non standard,
- non ha membri di dati non statici nella classe più derivata e al massimo una classe di base con membri di dati non statici oppure non ha classi di base con membri di dati non statici, e
- non ha classi base dello stesso tipo del primo membro dati non statico.
Una struttura di layout standard è una classe di layout standard definita con la struttura chiave di classe o la classe chiave di classe.
Un'unione di layout standard è una classe di layout standard definita con l'unione chiave di classe.
[ Nota: le classi di layout standard sono utili per comunicare con codice scritto in altri linguaggi di programmazione. Il loro layout è specificato in 9.2. —Endola nota ]
E vediamo alcuni esempi.
// empty classes have standard-layout
struct StandardLayout1 {};
struct StandardLayout2 {
int x;
};
struct StandardLayout3 {
private: // both are private, so it's ok
int x;
int y;
};
struct StandardLayout4 : StandardLayout1 {
int x;
int y;
void f(); // perfectly fine to have non-virtual functions
};
struct StandardLayout5 : StandardLayout1 {
int x;
StandardLayout1 y; // can have members of base type if they're not the first
};
struct StandardLayout6 : StandardLayout1, StandardLayout5 {
// can use multiple inheritance as long only
// one class in the hierarchy has non-static data members
};
struct StandardLayout7 {
int x;
int y;
StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};
struct StandardLayout8 {
public:
StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
int x;
};
struct StandardLayout9 {
int x;
static NonStandardLayout1 y; // no restrictions on static members
};
struct NonStandardLayout1 {
virtual f(); // cannot have virtual functions
};
struct NonStandardLayout2 {
NonStandardLayout1 X; // has non-standard-layout member
};
struct NonStandardLayout3 : StandardLayout1 {
StandardLayout1 x; // first member cannot be of the same type as base
};
struct NonStandardLayout4 : StandardLayout3 {
int z; // more than one class has non-static data members
};
struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class
Con queste nuove regole molti più tipi possono essere POD ora. E anche se un tipo non è POD, possiamo trarre vantaggio da alcune delle proprietà POD separatamente (se si tratta solo di un layout banale o standard).
La libreria standard ha tratti per testare queste proprietà nell'intestazione <type_traits>
:
template <typename T>
struct std::is_pod;
template <typename T>
struct std::is_trivial;
template <typename T>
struct std::is_trivially_copyable;
template <typename T>
struct std::is_standard_layout;
Possiamo fare riferimento allo standard Draft C ++ 14 per riferimento.
Questo è trattato nella sezione 8.5.1
Aggregati che ci fornisce la seguente definizione:
Un aggregato è un array o una classe (clausola 9) senza costruttori forniti dall'utente (12.1), senza membri di dati non statici privati o protetti (clausola 11), senza classi di base (clausola 10) e senza funzioni virtuali (10.3 ).
L'unica modifica è ora l'aggiunta di inizializzatori membri in classe non rende una classe non aggregata. Quindi il seguente esempio da dall'inizializzazione aggregata C ++ 11 per le classi con inizializzatori in-pace dei membri :
struct A
{
int a = 3;
int b = 3;
};
non era un aggregato in C ++ 11 ma è in C ++ 14. Questa modifica è coperta N3605: inizializzatori e aggregati dei membri , che presenta il seguente estratto:
Bjarne Stroustrup e Richard Smith hanno sollevato un problema relativo all'inizializzazione aggregata e agli inizializzatori dei membri che non funzionano insieme. Questo documento propone di risolvere il problema adottando la formulazione proposta da Smith che rimuove una restrizione secondo cui gli aggregati non possono avere inizializzatori di membri.
La definizione per la struttura POD ( semplici vecchi dati ) è trattata nella sezione 9
Classi che dice:
Una struttura POD 110 è una classe non sindacale che è sia una classe banale sia una classe di layout standard e non ha membri di dati non statici di tipo struct non-POD, unione non POD (o array di tali tipi). Allo stesso modo, un'unione POD è un'unione che è sia una classe banale che una classe di layout standard e non ha membri di dati non statici di tipo non strutt POD, unione non POD (o array di tali tipi). Una classe POD è una classe che è una struttura POD o un'unione POD.
che è la stessa formulazione di C ++ 11.
Come notato nel pod dei commenti, si basa sulla definizione di layout standard e questo è cambiato per C ++ 14, ma ciò avveniva tramite segnalazioni di difetti che venivano applicati a C ++ 14 dopo il fatto.
C'erano tre DR:
Quindi il layout standard è passato da questo Pre C ++ 14:
Una classe di layout standard è una classe che:
- (7.1) non ha membri di dati non statici di tipo classe di layout non standard (o array di tali tipi) o riferimenti,
- (7.2) non ha funzioni virtuali ([class.virtual]) e nessuna classe di base virtuale ([class.mi]),
- (7.3) ha lo stesso controllo di accesso (clausola [class.access]) per tutti i membri di dati non statici,
- (7.4) non ha classi base di layout non standard,
- (7.5) non ha membri di dati non statici nella classe più derivata e al massimo una classe di base con membri di dati non statici oppure non ha classi di base con membri di dati non statici e
- (7.6) non ha classi base dello stesso tipo del primo membro dati non statico.109
Una classe S è una classe di layout standard se:
- (3.1) non ha membri di dati non statici di tipo classe di layout non standard (o array di tali tipi) o riferimenti,
- (3.2) non ha funzioni virtuali e nessuna classe di base virtuale,
- (3.3) ha lo stesso controllo di accesso per tutti i membri di dati non statici,
- (3.4) non ha classi base di layout non standard,
- (3.5) ha al massimo un oggetto secondario della classe base di un dato tipo,
- (3.6) ha tutti i membri di dati e campi bit non statici nella classe e le sue classi base dichiarate per la prima volta nella stessa classe, e
- (3.7) non ha alcun elemento dell'insieme M (S) di tipi come classe base, dove per qualsiasi tipo X, M (X) è definito come segue.104 [Nota: M (X) è l'insieme dei tipi di tutti gli oggetti secondari di classe non base che possono trovarsi ad un offset zero in X. - nota finale]
- (3.7.1) Se X è un tipo di classe non sindacale senza membri di dati non statici (possibilmente ereditati), l'insieme M (X) è vuoto.
- (3.7.2) Se X è un tipo di classe non sindacale con un membro di dati non statico di tipo X0 che è di dimensione zero o è il primo membro di dati non statico di X (dove detto membro può essere un'unione anonima ), l'insieme M (X) è costituito da X0 e dagli elementi di M (X0).
- (3.7.3) Se X è un tipo di unione, l'insieme M (X) è l'unione di tutta M (Ui) e l'insieme contenente tutta l'Ui, dove ogni Ui è il tipo del suo membro di dati non statico di X .
- (3.7.4) Se X è un tipo di array con tipo di elemento Xe, l'insieme M (X) è costituito da Xe e dagli elementi di M (Xe).
- (3.7.5) Se X è un tipo non di classe, non di matrice, l'insieme M (X) è vuoto.
puoi per favore elaborare le seguenti regole:
Ci proverò:
a) le classi di layout standard devono avere tutti i membri di dati non statici con lo stesso controllo di accesso
E 'semplice: tutti i membri di dati non statici devono tutte essere public
, private
o protected
. Non puoi averne public
alcuniprivate
.
Il ragionamento per loro va al ragionamento per avere una distinzione tra "layout standard" e "layout non standard". Vale a dire, per dare al compilatore la libertà di scegliere come mettere le cose in memoria. Non si tratta solo di puntatori vtable.
All'epoca in cui standardizzavano il C ++ nel 98, dovevano praticamente prevedere in che modo le persone lo avrebbero implementato. Sebbene abbiano avuto un po 'di esperienza di implementazione con vari tipi di C ++, non erano sicuri delle cose. Così hanno deciso di essere cauti: dare ai compilatori la massima libertà possibile.
Ecco perché la definizione di POD in C ++ 98 è così rigorosa. Ha dato ai compilatori C ++ grande latitudine sul layout dei membri per la maggior parte delle classi. Fondamentalmente, i tipi di POD erano intesi come casi speciali, qualcosa che hai scritto specificamente per un motivo.
Quando si stava lavorando su C ++ 11, avevano molta più esperienza con i compilatori. E hanno capito che ... Gli autori di compilatori C ++ sono davvero pigri. Avevano tutta questa libertà, ma non lo fanno qualsiasi cosa con esso.
Le regole del layout standard sono più o meno codificanti per la pratica comune: la maggior parte dei compilatori non ha dovuto cambiare molto o nulla per implementarli (al di fuori di alcune cose per i tratti di tipo corrispondenti).
Ora, quando si è trattato di public
/ private
, le cose sono diverse. La libertà di riordinare quali membri sono public
vs.private
effettivamente può importare al compilatore, in particolare nelle build di debug. E poiché il punto del layout standard è che esiste compatibilità con altre lingue, non è possibile avere un layout diverso nel debug rispetto al rilascio.
Quindi c'è il fatto che non danneggia davvero l'utente. Se stai creando una classe incapsulata, le probabilità sono buone che tutti i membri dei tuoi dati lo saranno private
comunque. In genere non esponete membri di dati pubblici su tipi completamente incapsulati. Quindi questo sarebbe solo un problema per quei pochi utenti che vogliono farlo, che vogliono quella divisione.
Quindi non è una grande perdita.
b) solo una classe nell'intero albero ereditario può avere membri di dati non statici,
La ragione di ciò risale al motivo per cui hanno standardizzato nuovamente il layout standard: pratica comune.
Non c'è pratica comune quando si tratta di avere due membri di un albero ereditario che effettivamente memorizza le cose. Alcuni mettono la classe base prima del derivato, altri lo fanno nell'altro modo. In che modo ordini i membri se provengono da due classi base? E così via. I compilatori divergono notevolmente su queste domande.
Inoltre, grazie alla regola zero / one / infinity, una volta che dici che puoi avere due classi con i membri, puoi dirne quante ne vuoi. Ciò richiede l'aggiunta di molte regole di layout per come gestirlo. Devi dire come funziona l'ereditarietà multipla, quali classi mettono i loro dati prima di altre classi, ecc. Sono molte regole, per un guadagno materiale molto ridotto.
Non puoi creare tutto ciò che non ha funzioni virtuali e un layout standard predefinito del costruttore.
e il primo membro di dati non statico non può essere di un tipo di classe base (questo potrebbe violare le regole di aliasing).
Non posso davvero parlare con questo. Non sono abbastanza istruito nelle regole di aliasing di C ++ per capirlo davvero. Ma ha qualcosa a che fare con il fatto che il membro base condividerà lo stesso indirizzo della classe base stessa. Questo è:
struct Base {};
struct Derived : Base { Base b; };
Derived d;
static_cast<Base*>(&d) == &d.b;
E questo è probabilmente contro le regole di aliasing di C ++. In qualche modo.
Tuttavia, considerare questo: quanto utile potrebbe avere la possibilità di fare questo mai realmente essere? Poiché solo una classe può avere membri di dati non statici, allora Derived
deve essere quella classe (poiché ha Base
un membro come). Quindi Base
deve essere vuoto (di dati). E se Base
è vuoto, così come una classe di base ... perché avere un membro di dati?
Poiché Base
è vuoto, non ha stato. Quindi qualsiasi funzione membro non statica farà ciò che fa in base ai suoi parametri, non al this
puntatore.
Quindi di nuovo: nessuna grande perdita.
static_cast<Base*>(&d)
e &d.b
siano dello stesso Base*
tipo, indicano cose diverse che infrangono la regola di aliasing. Per favore, correggimi.
Derived
deve essere quella classe?
Derived
il primo membro sia la sua classe base, deve avere due cose: una classe base e un membro . E poiché solo una classe nella gerarchia può avere membri (ed essere comunque layout standard), ciò significa che la sua classe base non può avere membri.
Scarica qui la bozza finale dello standard internazionale C ++ 17 .
aggregati
C ++ 17 espande e migliora gli aggregati e l'inizializzazione degli aggregati. La libreria standard ora include anche una std::is_aggregate
classe tratto tratto. Ecco la definizione formale dalla sezione 11.6.1.1 e 11.6.1.2 (riferimenti interni elisi):
Un aggregato è un array o una classe con
- nessun costruttore fornito dall'utente, esplicito o ereditato,
- nessun membro di dati non statico privato o protetto,
- nessuna funzione virtuale e
- nessuna classe di base virtuale, privata o protetta.
[Nota: l'inizializzazione aggregata non consente l'accesso ai membri o ai costruttori della classe base protetta e privata. —End note]
Gli elementi di un aggregato sono:
- per una matrice, gli elementi della matrice in ordine crescente di pedice, oppure
- per una classe, le classi di base dirette in ordine di dichiarazione, seguite dai membri di dati non statici diretti che non sono membri di un'unione anonima, in ordine di dichiarazione.
Che cosa è cambiato?
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;
};
Classi Trivial
La definizione di banale classe è stata rielaborata in C ++ 17 per risolvere diversi difetti che non sono stati risolti in C ++ 14. I cambiamenti erano di natura tecnica. Ecco la nuova definizione alla 12.0.6 (riferimenti interni elisi):
Una classe banalmente copiabile è una classe:
- in cui ogni costruttore di copie, costruttore di spostamento, operatore di assegnazione di copie e operatore di assegnazione di spostamenti viene eliminato o banale,
- che abbia almeno un costruttore di copie non cancellato, costruttore di spostamento, operatore di assegnazione di copie, o spostare l'operatore di assegnazione e
- che ha un distruttore banale, non cancellato.
Una classe banale è una classe che è banalmente copiabile e ha uno o più costruttori predefiniti, tutti banali o eliminati e almeno uno dei quali non viene eliminato. [Nota: in particolare, una classe banalmente copiabile o banale non ha funzioni virtuali o classi di base virtuali. Nota finale]
I cambiamenti:
std::memcpy
. Questa era una contraddizione semantica, perché, definendo come cancellati tutti gli operatori di costruzione / assegnazione, il creatore della classe intendeva chiaramente che la classe non poteva essere copiata / spostata, eppure la classe incontrava ancora la definizione di una classe banalmente copiabile. Quindi in C ++ 17 abbiamo una nuova clausola che afferma che la classe banalmente copiabile deve avere almeno un banale operatore di costruzione / assegnazione di copia / spostamento non banale (anche se non necessariamente accessibile pubblicamente). Vedi N4148 , DR1734Classi di layout standard
Anche la definizione di layout standard è stata rielaborata per indirizzare i rapporti sui difetti. Ancora una volta i cambiamenti erano di natura tecnica. Ecco il testo dello standard (12.0.7). Come prima, i riferimenti interni sono elisi:
Una classe S è una classe di layout standard se:
- non ha membri di dati non statici di tipo classe di layout non standard (o array di tali tipi) o riferimenti,
- non ha funzioni virtuali e nessuna classe di base virtuale,
- ha lo stesso controllo di accesso per tutti i membri di dati non statici,
- non ha classi di base di layout non standard,
- ha al massimo un oggetto secondario di una classe di base di un dato tipo,
- ha tutti i membri di dati non statici e campi di bit in la classe e le sue classi base sono state dichiarate per la prima volta nella stessa classe e
- non ha alcun elemento dell'insieme M (S) di tipi (definito di seguito) come classe base.108 - Se X è un tipo di classe non sindacale con nessun ( possibilmente ereditato) membri di dati non statici, l'insieme M (X) è vuoto.
M (X) è definita come segue:
- Se X è un tipo di classe non sindacale il cui primo membro di dati non statici ha tipo X0 (dove detto membro può essere un'unione anonima), l'insieme M (X) è costituito da X0 e dagli elementi di M (X0).
- Se X è un tipo di unione, l'insieme M (X) è l'unione di tutta M (Ui) e l'insieme contenente tutta l'Ui, dove ogni Ui è il tipo del suo membro di dati non statico di X.
- Se X è un tipo di array con tipo di elemento Xe, l'insieme M (X) è costituito da Xe e gli elementi di M (Xe).
- Se X è un tipo non di classe, non di matrice, l'insieme M (X) è vuoto.
[Nota: M (X) è l'insieme dei tipi di tutti gli oggetti secondari di classe non base che sono garantiti in una classe di layout standard per essere a un offset zero in X. —end nota]
[Esempio:
—End esempio]struct B { int i; }; // standard-layout class struct C : B { }; // standard-layout class struct D : C { }; // standard-layout class struct E : D { char : 4; }; // not a standard-layout class struct Q {}; struct S : Q { }; struct T : Q { }; struct U : S, T { }; // not a standard-layout class
108) Ciò garantisce che due oggetti secondari che hanno lo stesso tipo di classe e che appartengono allo stesso oggetto più derivato non siano allocati allo stesso indirizzo.
I cambiamenti:
Nota: il comitato per gli standard C ++ intendeva applicare le modifiche di cui sopra basate sui rapporti sui difetti al C ++ 14, sebbene la nuova lingua non sia nello standard C ++ 14 pubblicato. È nello standard C ++ 17.
Seguendo il resto del chiaro tema di questa domanda, il significato e l'uso degli aggregati continua a cambiare con ogni standard. Ci sono diversi cambiamenti chiave all'orizzonte.
In C ++ 17, questo tipo è ancora un aggregato:
struct X {
X() = delete;
};
E quindi, X{}
si compila ancora perché si tratta di inizializzazione aggregata, non di una chiamata del costruttore. Vedi anche: Quando un costruttore privato non è un costruttore privato?
In C ++ 20, la limitazione cambierà dal richiedere:
nessun
explicit
costruttore fornito dall'utente o ereditato
per
nessun costruttore dichiarato dall'utente o ereditato
Questo è stato adottato nel progetto di lavoro C ++ 20 . Né X
qui né C
nella domanda collegata saranno aggregati in C ++ 20.
Questo crea anche un effetto yo-yo con il seguente esempio:
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
In C ++ 11/14, nonB
era un aggregato a causa della classe base, quindi esegue l'inizializzazione del valore che chiama quali chiamate , in un punto in cui è accessibile. Questo è stato ben formato.B{}
B::B()
A::A()
In C ++ 17, B
divenne un aggregato perché erano consentite le classi di base, il che rendeva l' B{}
inizializzazione aggregata. Ciò richiede l'inizializzazione dell'elenco copie A
da {}
, ma dall'esterno del contesto di B
, dove non è accessibile. In C ++ 17, questo è mal formato ( auto x = B();
andrebbe bene però).
In C ++ 20 ora, a causa della modifica della regola sopra, B
cessa ancora una volta di essere un aggregato (non a causa della classe base, ma a causa del costruttore predefinito dichiarato dall'utente - anche se è predefinito). Quindi siamo tornati a esaminare B
il costruttore e questo frammento diventa ben formato.
Un problema comune che si presenta è voler usare emplace()
costruttori in stile con aggregati:
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
Questo non funziona, perché emplace
tenterà di eseguire efficacemente l'inizializzazioneX(1, 2)
, che non è valida. La soluzione tipica è quella di aggiungere un costruttore X
, ma con questa proposta (che sta attualmente lavorando attraverso Core), gli aggregati avranno effettivamente sintetizzati costruttori che fanno la cosa giusta - e si comporteranno come normali costruttori. Il codice sopra verrà compilato così com'è in C ++ 20.
In C ++ 17, questo non viene compilato:
template <typename T>
struct Point {
T x, y;
};
Point p{1, 2}; // error
Gli utenti dovrebbero scrivere la propria guida alla detrazione per tutti i modelli aggregati:
template <typename T> Point(T, T) -> Point<T>;
Ma poiché in un certo senso questa è "la cosa più ovvia" da fare, ed è fondamentalmente solo una piastra, il linguaggio lo farà per te. Questo esempio verrà compilato in C ++ 20 (senza la necessità della guida alla detrazione fornita dall'utente).