Vraag Wat zijn aggregaten en POD's en hoe / waarom zijn ze speciaal?


Deze FAQ gaat over aggregaten en POD's en heeft betrekking op het volgende materiaal:

  • Wat zijn aggregaten?
  • Wat zijn PEULs (Plain Old Data)?
  • Hoe zijn ze verwant?
  • Hoe en waarom zijn ze speciaal?
  • Welke veranderingen voor C ++ 11?

445
2017-11-14 15:35


oorsprong


antwoorden:


Hoe te lezen:

Dit artikel is vrij lang. Als u meer wilt weten over zowel aggregaten als POD's (Plain Old Data), neem dan de tijd en lees het. Als u alleen geïnteresseerd bent in aggregaten, lees dan alleen het eerste deel. Als u alleen geïnteresseerd bent in POD's, moet u eerst de definitie, implicaties en voorbeelden van aggregaten en vervolgens uzelf lezen mei spring naar PODs, maar ik raad toch aan het eerste deel in zijn geheel te lezen. Het begrip aggregaten is essentieel voor het definiëren van POD's. Als je fouten tegenkomt (zelfs ondergeschikt, inclusief grammatica, stilistiek, opmaak, syntaxis, etc.), laat dan een reactie achter, ik zal deze bewerken.

Wat zijn aggregaten en waarom ze speciaal zijn

Formele definitie van de C ++ -standaard (C ++ 03 8.5.1 §1):

Een aggregaat is een array of een klasse (clausule 9) zonder gedeclareerd door de gebruiker   constructors (12.1), geen private of beschermde niet-statische data-leden (clausule 11),   geen basisklassen (clausule 10) en geen virtuele functies (10.3).

Oké, laten we deze definitie doorgronden. Allereerst is elke array een aggregaat. Een klasse kan ook een aggregaat zijn als ... wacht! niets wordt gezegd over structs of vakbonden, kunnen het geen aggregaten zijn? Ja, dat kunnen ze. In C ++, de term class verwijst naar alle klassen, structs en vakbonden. Een klasse (of struct of union) is dus een aggregaat als en alleen als het voldoet aan de criteria uit de bovenstaande definities. Wat betekenen deze criteria?

  • Dit betekent niet dat een geaggregeerde klasse geen constructors kan hebben, in feite kan deze een standaardconstructor en / of een kopieconstructor hebben, zolang ze impliciet worden gedeclareerd door de compiler en niet expliciet door de gebruiker

  • Geen privé of beschermd niet-statische gegevensleden. U kunt zoveel privé- en beschermde lidfuncties (maar geen constructeurs) hebben als evenveel privé- als beveiligde leden statisch data-leden en lidfuncties zoals u wilt en niet de regels voor geaggregeerde klassen schenden

  • Een geaggregeerde klasse kan een door de gebruiker gedeclareerde / door de gebruiker gedefinieerde kopie-toewijzingsoperator en / of destructor hebben

  • Een array is een aggregaat, zelfs als het een array van niet-geaggregeerde klassen is.

Laten we nu eens enkele voorbeelden bekijken:

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

Je snapt het idee. Laten we nu eens kijken hoe aggregaten speciaal zijn. Ze kunnen, in tegenstelling tot niet-geaggregeerde klassen, worden geïnitialiseerd met accolades {}. Deze initialisatiesyntaxis is algemeen bekend voor arrays en we hebben zojuist geleerd dat dit aggregaten zijn. Dus laten we beginnen met hen.

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

if (m == n)
   de ith element van de array wordt geïnitialiseerd met eenik
anders als (m <n)
de eerste m-elementen van de array worden geïnitialiseerd met een1, een2, …, eenm en de andere n - m elementen zijn, indien mogelijk, -Waarde geïnitialiseerd (zie hieronder voor de uitleg van de term)
anders als (m> n)
   de compiler geeft een foutmelding
anders  (dit is het geval wanneer n helemaal niet is opgegeven zoals int a[] = {1, 2, 3};)
 de grootte van de array (n) wordt verondersteld gelijk te zijn aan m, dus int a[] = {1, 2, 3}; is gelijk aan int a[3] = {1, 2, 3};

Wanneer een object van scalair type (bool, int, char, double, wijzers, etc.) is -Waarde geïnitialiseerd het betekent dat het is geïnitialiseerd met 0 voor dat type (false voor bool, 0.0 voor double, enz.). Wanneer een object van het klassetype met een door de gebruiker opgegeven standaardconstructor de waarde-geïnitialiseerd heeft, wordt zijn standaardconstructor aangeroepen. Als de standaardconstructor impliciet is gedefinieerd, worden alle niet-statische leden recursief met waarde geïnitialiseerd. Deze definitie is onnauwkeurig en een beetje onjuist, maar het zou u het basisidee moeten geven. Een referentie kan niet value-geïnitialiseerd zijn. Waarde-initialisatie voor een niet-geaggregeerde klasse kan mislukken als de klasse bijvoorbeeld geen geschikte standaardconstructor heeft.

Voorbeelden van array-initialisatie:

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
}

Laten we nu eens kijken hoe geaggregeerde klassen kunnen worden geïnitialiseerd met accolades. Vrijwel dezelfde manier. In plaats van de arrayelementen zullen we de niet-statische gegevensleden initialiseren in de volgorde waarin ze voorkomen in de klassedefinitie (ze zijn allemaal per definitie openbaar). Als er minder initializers zijn dan leden, is de rest value-geïnitialiseerd. Als het onmogelijk is om een ​​van de leden die niet expliciet zijn geïnitialiseerd, te initialiseren, krijgen we een compilatiefout. Als er meer initializers zijn dan noodzakelijk, krijgen we ook een fout in de compileertijd.

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 het bovenstaande voorbeeld y.c is geïnitialiseerd met 'a', y.x.i1 met 10, y.x.i2 met 20, y.i[0] met 20, y.i[1] met 30 en y.f is value-geïnitialiseerd, dat wil zeggen geïnitialiseerd met 0.0. Het beschermde statische lid d is helemaal niet geïnitialiseerd, omdat het is static.

Geaggregeerde vakbonden zijn verschillend omdat u alleen hun eerste lid met accolades kunt initialiseren. Ik denk dat als je voldoende gevorderd bent in C ++ om zelfs maar te overwegen om vakbonden te gebruiken (hun gebruik kan erg gevaarlijk zijn en moet zorgvuldig worden overwogen), je zou zelf de regels voor vakbonden in de standaard kunnen opzoeken :).

Nu we weten wat er speciaal is aan aggregaten, proberen we de beperkingen voor klassen te begrijpen; dat is, waarom ze er zijn. We moeten begrijpen dat ledige initialisatie met accolades inhoudt dat de klasse niets meer is dan de som van de leden. Als een door de gebruiker gedefinieerde constructor aanwezig is, betekent dit dat de gebruiker wat extra werk moet doen om de leden te initialiseren, daarom zou brace-initialisatie niet kloppen. Als virtuele functies aanwezig zijn, betekent dit dat de objecten van deze klasse (op de meeste implementaties) een verwijzing hebben naar de zogenaamde vtable van de klasse, die is ingesteld in de constructor, dus brace-initialisatie zou onvoldoende zijn. Je zou de rest van de beperkingen op dezelfde manier kunnen berekenen als een oefening :).

Zo genoeg over de aggregaten. Nu kunnen we een strikter stel typen definiëren, namelijk POD's

Wat zijn POD's en waarom zijn ze speciaal?

Formele definitie van de C ++ -standaard (C ++ 03 9 §4):

Een POD-structuur is een verzamelklasse   die geen niet-statische gegevensleden heeft van   type non-POD-struct, non-POD-union (of   reeks van dergelijke typen) of verwijzing, en   heeft geen door de gebruiker gedefinieerde kopieertoewijzing   operator en geen door de gebruiker gedefinieerd   destructor. Op dezelfde manier is een POD-unie   een geaggregeerde vereniging die nee heeft   niet-statische gegevensleden van het type   niet-POD-struct, non-POD-union (of   reeks van dergelijke typen) of verwijzing, en   heeft geen door de gebruiker gedefinieerde kopieertoewijzing   operator en geen door de gebruiker gedefinieerd   destructor. Een POD-klasse is een klasse   dat is een POD-structuur of een   POD-union.

Wauw, dit is moeilijker om te ontleden, is het niet? :) Laten we vakbonden weglaten (op dezelfde gronden als hierboven) en op een iets duidelijkere manier herformuleren:

Een verzamelde klasse wordt een POD genoemd als   het heeft geen door de gebruiker gedefinieerde kopie-toewijzing   operator en destructor en geen van   haar niet-statische leden zijn niet-POD   klasse, array van niet-POD, of a   referentie.

Wat houdt deze definitie in? (Had ik al gezegd PEUL betekent Normale oude gegevens?)

  • Alle POD-klassen zijn aggregaten, of om het andersom te zeggen, als een klasse geen aggregaat is, dan is het zeker geen POD
  • Klassen, net als structs, kunnen POD's zijn, hoewel de standaardterm in beide gevallen POD-struct is
  • Net als in het geval van aggregaten maakt het niet uit welke statische leden de klasse heeft

Voorbeelden:

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-unies, scalaire typen en arrays van dergelijke typen worden gezamenlijk genoemd POD-types.
POD's zijn op veel manieren speciaal. Ik zal slechts enkele voorbeelden geven.

  • POD-klassen zijn het dichtst bij C structs. In tegenstelling tot hen, kunnen POD's ledenfuncties en willekeurige statische leden hebben, maar geen van deze twee veranderen de geheugenlay-out van het object. Dus als u een min of meer draagbare dynamische bibliotheek wilt schrijven die kan worden gebruikt vanuit C en zelfs .NET, moet u proberen al uw geëxporteerde functies te laten uitvoeren en alleen parameters van POD-typen te retourneren.

  • De levensduur van objecten van een niet-POD-klassentype begint wanneer de constructor is voltooid en eindigt wanneer de destructor is voltooid. Voor POD-klassen begint de levensduur wanneer de opslag voor het object bezet is en eindigt wanneer die opslag wordt vrijgegeven of opnieuw wordt gebruikt.

  • Voor objecten van POD-typen wordt gegarandeerd door de standaard dat wanneer u memcpy de inhoud van je object in een array van char of unsigned char, en dan memcpy de inhoud terug in uw object, het object behoudt zijn oorspronkelijke waarde. Houd er rekening mee dat een dergelijke garantie niet bestaat voor objecten van niet-POD-typen. U kunt ook veilig POD-objecten kopiëren met memcpy. In het volgende voorbeeld wordt verondersteld dat T een POD-type is:

    #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
    
  • ga naar verklaring. Zoals je wellicht weet, is het illegaal (de compiler moet een fout genereren) om een ​​sprong te maken via goto vanaf een punt waar een variabele nog niet in scope was tot een punt waar het al in scope is. Deze beperking is alleen van toepassing als de variabele van het niet-POD-type is. In het volgende voorbeeld f()is slecht gevormd terwijl g() is goed gevormd. Merk op dat de compiler van Microsoft te liberaal is met deze regel - het geeft in beide gevallen een waarschuwing.

    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;
    }
    
  • Het is gegarandeerd dat er geen padding aan het begin van een POD-object is. Met andere woorden, als een eerste lid van de POD-klasse A van het type T is, kunt u veilig reinterpret_cast van A* naar T* en krijg de aanwijzer naar het eerste lid en omgekeerd.

De lijst gaat maar door ...

Conclusie

Het is belangrijk om te begrijpen wat een POD precies is, omdat veel taalfuncties zich anders gedragen dan u ziet.


469
2017-08-25 11:48



Welke veranderingen voor C ++ 11?

aggregaten

De standaarddefinitie van een aggregaat is enigszins gewijzigd, maar het is nog steeds vrijwel hetzelfde:

Een aggregaat is een array of een klasse (clausule 9) zonder constructors door de gebruiker (12.1),   Nee brace-of-gelijke-initialiseerders voor niet-statische gegevensleden (9.2), niet privé of beschermd   niet-statische gegevensleden (clausule 11), geen basisklassen (clausule 10) en geen virtuele functies (10.3).

Ok, wat is er veranderd?

  1. Voorheen kon een aggregaat nee hebben -User verklaard constructeurs, maar nu kan het dat niet Gebruiker opgegeven constructeurs. Is er een verschil? Ja, dat is zo, omdat je nu constructeurs kunt verklaren en standaard hen:

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

    Dit is nog steeds een aggregaat omdat een constructeur (of een speciale ledenfunctie) dat is standaard op de eerste aangifte wordt niet door de gebruiker verstrekt.

  2. Nu kan een aggregaat er geen hebben brace-of-gelijke-initialiseerders voor niet-statische gegevensleden. Wat betekent dit? Nou, dit is gewoon omdat we met deze nieuwe standaard leden in deze klasse kunnen initialiseren:

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

    Als u deze functie gebruikt, is de klasse niet langer een aggregaat omdat deze in principe gelijk is aan het bieden van uw eigen standaardconstructor.

Wat een aggregaat is, veranderde dus niet veel. Het is nog steeds hetzelfde basisidee, aangepast aan de nieuwe functies.

Hoe zit het met POD's?

POD's hebben veel veranderingen doorgemaakt. Veel van de vorige regels over POD's waren versoepeld in deze nieuwe standaard en de manier waarop de definitie in de standaard wordt gegeven, is radicaal veranderd.

Het idee van een POD is om in feite twee verschillende eigenschappen vast te leggen:

  1. Het ondersteunt statische initialisatie en
  2. Compileren van een POD in C ++ geeft je dezelfde geheugenlay-out als een struct samengesteld in C.

Daarom is de definitie opgesplitst in twee verschillende concepten: triviaal klassen en Standaard opmaak klassen, omdat deze nuttiger zijn dan POD. De standaard gebruikt nu zelden de term POD en geeft de voorkeur aan de meer specifieke triviaal en Standaard opmaak concepten.

De nieuwe definitie zegt in feite dat een POD een klasse is die zowel triviaal is als een standaardindeling heeft, en deze eigenschap moet recursief gelden voor alle niet-statische gegevensleden:

Een POD-structuur is een niet-verbondsklasse die zowel een triviale klasse als een standaardindelingklasse is,   en heeft geen niet-statische gegevensleden van het type niet-POD-struct, niet-POD-unie (of reeks van dergelijke typen).   Evenzo is een POD-unie een unie die zowel een triviale klasse als een standaard lay-outklasse is en heeft   geen niet-statische gegevensleden van het type non-POD struct, non-POD union (of array van dergelijke typen).   Een POD-klasse is een klasse die een POD-structuur of een POD-unie is.

Laten we elk van deze twee eigenschappen afzonderlijk in detail bekijken.

Triviale klassen

Triviaal is de eerste hierboven genoemde eigenschap: triviale klassen ondersteunen statische initialisatie. Als een klasse triviaal kopieerbaar is (een verzameling van triviale klassen), is het goed om de weergave ervan over de plaats te kopiëren met dingen zoals memcpy en verwachten dat het resultaat hetzelfde is.

De standaard definieert een triviale klasse als volgt:

Een triviaal kopieerbare klasse is een klasse die:

- heeft geen niet-triviale kopieconstructeurs (12.8),

- heeft geen niet-triviale verhuizers (12.8),

- heeft geen niet-triviale kopie toewijzingsoperatoren (13.5.3, 12.8),

- heeft geen niet-triviale toewijzingsoperatoren voor verplaatsingen (13.5.3, 12.8) en

- heeft een triviale destructor (12.4).

Een triviale klasse is een klasse met een triviale standaardconstructor (12.1) en kan triviaal worden gekopieerd.

[ Notitie: In het bijzonder heeft een triviaal kopieerbare of triviale klasse geen virtuele functies   of virtuele basisklassen.-Inde notitie ]

Dus, wat zijn al die triviale en niet-triviale dingen?

Een copy / move-constructor voor klasse X is triviaal als deze niet door de gebruiker wordt geleverd en als

- klasse X heeft geen virtuele functies (10.3) en geen virtuele basisklassen (10.1), en

- de constructor die geselecteerd is om elk direct basisklasse-subobject te kopiëren / verplaatsen is triviaal, en

- voor elk niet-statisch gegevenslid van X dat van het klasse type is (of een array daarvan), de constructor   geselecteerd om dat lid te kopiëren / verplaatsen is triviaal;

anders is de constructeur kopiëren / verplaatsen niet-triviaal.

In feite betekent dit dat een constructeur van een kopie of een verplaatsing triviaal is als deze niet door de gebruiker wordt geleverd, de klasse niets virtueel bevat en deze eigenschap recursief geldt voor alle leden van de klasse en voor de basisklasse.

De definitie van een triviale copy / move-toewijzingsoperator lijkt erg op elkaar, simpelweg door het woord "constructor" te vervangen door "assignment-operator".

Een triviale destructor heeft ook een vergelijkbare definitie, met de toegevoegde beperking dat het niet virtueel kan zijn.

En nog een soortgelijke regel bestaat voor triviale standaardconstructors, met de toevoeging dat een standaardconstructor niet-triviaal is als de klasse niet-statische gegevensleden heeft met brace-of-gelijke-initialiseerders, wat we hierboven hebben gezien.

Hier zijn enkele voorbeelden om alles op te ruimen:

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

Standaard opmaak

Standaard opmaak is de tweede eigenschap. De standaard vermeldt dat deze nuttig zijn voor communicatie met andere talen, en dat komt omdat een standaardlay-outklasse dezelfde geheugenlay-out heeft van de equivalente C struct of union.

Dit is een andere eigenschap die recursief moet worden vastgehouden voor leden en alle basisklassen. En zoals gebruikelijk zijn geen virtuele functies of virtuele basisklassen toegestaan. Dat zou de lay-out incompatibel maken met C.

Een ontspannen regel hierbij is dat standaardindelingklassen alle niet-statische gegevensleden moeten hebben met hetzelfde toegangsbeheer. Voorheen moesten dit allemaal zijn openbaar, maar nu kun je ze privé of beschermd maken, zolang ze dat maar zijn alle privé of alle beveiligd.

Bij overerving, maar een klasse in de gehele overervingsboom kan niet-statische gegevensleden hebben en het eerste niet-statische gegevenslid kan niet van een basisklassetype zijn (dit zou aliassenregels kunnen doorbreken), anders is het geen standaardlay-outklasse.

Zo gaat de definitie in de standaardtekst:

Een standaard lay-outklasse is een klasse die:

- heeft geen niet-statische gegevens leden van het type niet-standaard lay-outklasse (of reeks van dergelijke typen)   of referentie,

- heeft geen virtuele functies (10.3) en geen virtuele basisklassen (10.1),

- heeft dezelfde toegangscontrole (clausule 11) voor alle niet-statische gegevensleden,

- heeft geen niet-standaard lay-out basisklassen,

- heeft geen niet-statische gegevensleden in de meest afgeleide klasse en hoogstens één basisklasse met   niet-statische gegevensleden, of heeft geen basisklassen met niet-statische gegevensleden, en

- heeft geen basisklassen van hetzelfde type als het eerste niet-statische gegevenslid.

Een standaardlay-outstructuur is een standaardlay-outklasse die is gedefinieerd met de class-key struct of   de class-key klasse.

Een standaard lay-out-unie is een standaard lay-outklasse gedefinieerd met de klasse-sleutelunie.

[ Notitie: Standaardlay-outklassen zijn handig voor communicatie met code die in andere programmeertalen is geschreven. Hun lay-out is gespecificeerd in 9.2.-Inde notitie ]

En laten we een paar voorbeelden zien.

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

Conclusie

Met deze nieuwe regels kunnen nu veel meer typen POD's zijn. En zelfs als een type geen POD is, kunnen we sommige van de POD-eigenschappen afzonderlijk gebruiken (als het slechts een van triviale of standaardlay-out is).

De standaardbibliotheek heeft eigenschappen om deze eigenschappen in de koptekst te testen <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;

380
2017-12-16 18:21



Wat is er veranderd voor C ++ 14

We kunnen verwijzen naar de Concept C ++ 14 standaard als referentie.

aggregaten

Dit is behandeld in sectie 8.5.1  aggregaten wat ons de volgende definitie geeft:

Een aggregaat is een array of een klasse (clausule 9) zonder door de gebruiker verstrekt   constructors (12.1), geen persoonlijke of beschermde niet-statische gegevensleden   (Clausule 11), geen basisklassen (clausule 10) en geen virtuele functies   (10.3).

De enige verandering is nu toevoegen in-class lid-initializers maakt een klasse niet-geaggregeerd. Dus het volgende voorbeeld uit C ++ 11 aggregate initialization voor klassen met member in-pace initializers:

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

was geen aggregaat in C ++ 11 maar het is in C ++ 14. Deze wijziging is opgenomen in N3605: initializers en aggregaten van leden, die het volgende abstract heeft:

Bjarne Stroustrup en Richard Smith brachten een probleem met betrekking tot geaggregeerde   initialisatie en lid-initializers werken niet samen. Deze   papier stelt voor het probleem op te lossen door de door Smith voorgestelde formulering over te nemen   dat verwijdert een beperking die aggregaten niet kunnen hebben   -lid initialiseerders.

POD blijft hetzelfde

De definitie voor POD (duidelijke oude gegevens) struct is behandeld in sectie 9  Klassen waarop staat:

Een POD struct110 is een niet-verbondsklasse die zowel een triviale klas is als   een standaard lay-outklasse en heeft geen niet-statische gegevensleden van het type   niet-POD-structuur, niet-POD-unie (of reeks van dergelijke typen). Evenzo, a   POD-unie is een unie die zowel een triviale klasse als een is   standaard lay-outklasse en heeft geen niet-statische gegevensleden van het type   niet-POD-structuur, niet-POD-unie (of reeks van dergelijke typen). Een POD-klasse is   een klasse die een POD-structuur of een POD-unie is.

wat dezelfde tekst is als C ++ 11.


74
2018-02-28 23:26



kunt u de volgende regels alstublieft uitwerken:

Ik zal het proberen:

a) Standaardlay-outklassen moeten alle niet-statische gegevensleden hebben met hetzelfde toegangsbeheer

Dat is eenvoudig: alle niet-statische gegevens moeten dat doen alle worden public, privateof protected. Je kunt er geen hebben public en een beetje private.

De redenering voor hen gaat naar de redenering voor het maken van een onderscheid tussen "standaard lay-out" en "niet standaard lay-out" helemaal. Namelijk, om de compiler de vrijheid te geven om te kiezen hoe dingen in het geheugen te plaatsen. Het gaat niet alleen om vtable-aanwijzers.

Toen ze C ++ in 98 hadden gestandaardiseerd, moesten ze in principe voorspellen hoe mensen het zouden implementeren. Hoewel ze nogal wat implementatie-ervaring hadden met verschillende smaken van C ++, waren ze niet zeker over dingen. Dus besloten ze voorzichtig te zijn: geef de compilers zoveel mogelijk vrijheid.

Daarom is de definitie van POD in C ++ 98 zo streng. Het gaf C ++ -compilers grote speelruimte bij de lay-out van leden voor de meeste klassen. Kortom, POD-typen waren bedoeld als speciale gevallen, iets dat je met een reden schreef.

Toen C ++ 11 werd bewerkt, hadden ze veel meer ervaring met compilers. En ze beseften dat ... C ++ compiler-schrijvers echt lui zijn. Ze hadden al deze vrijheid, maar dat deden ze niet do alles erbij.

De regels van de standaardlay-out zijn min of meer codificerende gangbare praktijken: de meeste compilers hoefden niet veel te veranderen of helemaal niet om ze te implementeren (buiten misschien iets voor de overeenkomstige typekenmerken).

Nu, als het ging om public/private, dingen zijn anders. De vrijheid om te herordenen welke leden zijn public vs. private kan eigenlijk van belang zijn voor de compiler, met name in builds voor foutopsporing. En aangezien de standaardlay-out is dat er compatibiliteit is met andere talen, kunt u de lay-out niet anders laten zijn in debug vs. release.

Dan is er het feit dat het de gebruiker niet echt pijn doet. Als u een ingekapselde klasse maakt, zijn de kansen goed dat al uw gegevensleden aanwezig zullen zijn private hoe dan ook. Over het algemeen worden openbare gegevensleden niet blootgesteld aan volledig ingekapselde typen. Dus dit zou alleen een probleem zijn voor die paar gebruikers die dat willen doen, die die verdeling willen.

Het is dus geen groot verlies.

b) slechts één klasse in de gehele overervingsboom kan niet-statische gegevensleden hebben,

De reden voor deze komt terug op de reden waarom ze de standaardlay-out opnieuw standaardiseerden: algemene praktijk.

er is Nee algemene praktijk als het gaat om het hebben van twee leden van een overervingsboom die de dingen daadwerkelijk opslaan. Sommigen zetten de basisklasse vóór de afgeleide, anderen doen het de andere kant op. Welke manier bestelt u de leden als ze uit twee basisklassen komen? Enzovoort. Compilers verschillen sterk van deze vragen.

Dankzij de nul / een / oneindigheidsregel kun je, als je zegt dat je twee klassen met leden kunt hebben, er zoveel zeggen als je wilt. Dit vereist het toevoegen van veel lay-outregels voor het omgaan met dit. Je moet zeggen hoe multiple inheritance werkt, welke klassen hun gegevens vóór andere klassen plaatsen, enz. Dat zijn veel regels, voor heel weinig materieel gewin.

U kunt niet alles maken dat geen virtuele functies heeft en standaard een standaardindeling voor constructeurs.

en het eerste niet-statische gegevenslid kan niet van het basisklasse type zijn (dit zou de aliasingregels kunnen doorbreken).

Ik kan hier niet echt tegen spreken. Ik ben niet voldoende opgeleid in de aliasingregels van C ++ om het echt te begrijpen. Maar het heeft iets te maken met het feit dat het basislid hetzelfde adres deelt als de basisklasse zelf. Dat is:

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

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

En dat is waarschijnlijk tegen de aliasingregels van C ++. In zekere zin.

Overweeg dit echter: hoe nuttig zou het kunnen zijn om dit ooit te doen werkelijk worden? Omdat slechts één klasse niet-statische gegevensleden kan hebben, dan Derived moet die klasse zijn (aangezien het een Base als lid). Zo Base  moet leeg zijn (van gegevens). En als Base is leeg, net zoals een basisklasse ... waarom is er een gegevenslid?

Sinds Base is leeg, het heeft geen status. Dus alle niet-statische ledenfuncties zullen doen wat ze doen op basis van hun parameters, niet hun this wijzer.

Dus nogmaals: geen groot verlies.


36
2018-06-10 17:06



Veranderingen in C ++ 17

Download de definitieve versie van de C ++ 17 International Standard hier.

aggregaten

C ++ 17 breidt en verbetert aggregaten en totale initialisatie. De standaardbibliotheek bevat nu ook een std::is_aggregate type eigenschapsklasse. Hier is de formele definitie uit sectie 11.6.1.1 en 11.6.1.2 (interne verwijzingen voorzien):

Een aggregaat is een array of een klasse met
   - geen door de gebruiker geleverde, expliciete of overgenomen constructeurs,
   - geen private of beschermde niet-statische data-leden,
   - geen virtuele functies, en
   - geen virtuele, private of beschermde basisklassen.
  [Opmerking: Aggregatie initialisatie staat geen toegang toe tot beschermde leden en constructeurs van beschermde basisgroepen of leden van de private basisklasse. -End briefje]
  De elementen van een aggregaat zijn:
   - voor een array, de array-elementen in toenemende volgorde van subscript, of
   - voor een klasse, de directe basisklassen in declaratie volgorde, gevolgd door de directe niet-statische gegevensleden die geen lid zijn van een anonieme unie, in een declaratie volgorde.

Wat veranderde?

  1. Aggregaten kunnen nu openbare, niet-virtuele basisklassen hebben. Verder is het niet een vereiste dat basisklassen aggregaten zijn. Als ze geen aggregaten zijn, worden ze geïnitialiseerd.
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::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. Explicit defaulted constructors zijn niet toegestaan
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Inheriting constructors zijn niet toegestaan
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Triviale klassen

De definitie van triviale klasse is herwerkt in C ++ 17 om verschillende defecten aan te pakken die niet zijn behandeld in C ++ 14. De veranderingen waren technisch van aard. Hier is de nieuwe definitie op 12.0.6 (interne verwijzingen):

Een triviaal kopieerbare klasse is een klasse:
   - waarbij elke copy-constructor, verplaatsingsconstructor, operator voor kopieertaak en verplaats-toewijzingsoperator ofwel verwijderd of triviaal is,
   - die ten minste één niet-verwijderde copy-constructor, verplaatsingsconstructor, kopie-toewijzingsoperator of verplaatsingsopdrachtoperator heeft, en
   - dat heeft een triviale, niet-verwijderde destructor.
  Een triviale klasse is een klasse die triviaal te kopiëren is en een of meer standaardconstructors heeft, die allemaal triviaal of verwijderd zijn en waarvan er ten minste één niet is verwijderd. [Opmerking: in het bijzonder, een triviaal kopieerbaar   of triviale klasse heeft geen virtuele functies of virtuele basisklassen. - eindnoot]

Veranderingen:

  1. Onder C ++ 14 kon de klasse voor een klasse niet triviaal zijn, zodat er geen copy / move-constructor / toewijzingsoperators waren die niet-triviaal waren. Maar dan impliciet verklaard als standaard constructor / operator kan niet-triviaal zijn en toch bepaald zoals verwijderd omdat de klasse bijvoorbeeld een subobject van het klassetype bevatte dat niet kon worden gekopieerd / verplaatst. De aanwezigheid van zo'n niet-triviale, gedefinieerde als verwijderde constructor / operator zou ervoor zorgen dat de hele klasse niet-triviaal is. Een soortgelijk probleem bestond met destructors. C ++ 17 verduidelijkt dat de aanwezigheid van een dergelijke constructor / operators niet veroorzaakt dat de klasse niet-triviaal kopieerbaar is, en daarom niet-triviaal, en dat een triviaal te kopiëren klasse een triviale, niet-verwijderde destructor moet hebben. DR1734, DR1928
  2. C ++ 14 stond een triviaal te kopiëren klasse toe, vandaar een triviale klasse, om elke copy / move-constructor / toewijzingsoperator gedeclareerd als verwijderd te laten verklaren. Als een dergelijke klasse ook een standaardlay-out is, kan deze echter legaal worden gekopieerd / verplaatst std::memcpy. Dit was een semantische tegenstrijdigheid, omdat de maker van de klasse door het definiëren als verwijderde alle constructor / toewijzingsoperatoren duidelijk bedoelde dat de klasse niet kon worden gekopieerd / verplaatst, maar toch voldeed de klasse nog aan de definitie van een triviaal kopieerbare klasse. Daarom hebben we in C ++ 17 een nieuwe clausule waarin staat dat trivially kopieerbare klasse minstens één triviale, niet-verwijderde (hoewel niet noodzakelijk openbaar toegankelijke) copy / move constructor / assignment-operator moet hebben. Zien N4148, DR1734
  3. De derde technische wijziging betreft een soortgelijk probleem met standaardconstructeurs. Onder C ++ 14 zou een klasse triviale standaardconstructors kunnen hebben die impliciet als verwijderd waren gedefinieerd, maar toch een triviale klasse zijn. De nieuwe definitie verduidelijkt dat een triviale klasse ten minste één triviale, niet-verwijderde standaardconstructor moet hebben. Zien DR1496

Klassen met standaard lay-out

De definitie van standaardlay-out is ook herwerkt om defectrapporten aan te pakken. Wederom waren de veranderingen technisch van aard. Hier is de tekst uit de standaard (12.0.7). Zoals eerder werden interne referenties uitgelicht:

Een klasse S is een klasse met standaardlay-out als deze:
   - heeft geen niet-statische gegevensleden van het type niet-standaard lay-outklasse (of reeks van dergelijke typen) of verwijzing,
   - heeft geen virtuele functies en geen virtuele basisklassen,
   - heeft dezelfde toegangscontrole voor alle niet-statische gegevensleden,
   - heeft geen niet-standaard lay-out basisklassen,
   - heeft hoogstens één basisklasse subobject van een bepaald type,
   - heeft alle niet-statische gegevensleden en bitvelden in de klasse en de bijbehorende basisklassen worden eerst in dezelfde klasse gedeclareerd, en
   - heeft geen element van de set M (S) van typen (hieronder gedefinieerd) als een basisklasse.108
  M (X) is als volgt gedefinieerd:
   - Als X een niet-samengetrokken klassetype is met geen (mogelijk overgeërfde) niet-statische gegevensleden, is de set M (X) leeg.
   - Als X een niet-verbindingsclassetype is waarvan het eerste niet-statische gegevenslid type X0 heeft (waarbij genoemd lid een anonieme unie kan zijn), bestaat de verzameling M (X) uit X0 en de elementen uit M (X0).
   - Als X een union-type is, is de set M (X) de unie van alle M (Ui) en de set die alle Ui bevat, waarbij elke Ui het type is van het ith niet-statische data-lid van X.
   - Als X een arraytype met elementtype Xe is, bestaat de set M (X) uit Xe en de elementen van M (Xe).
   - Als X een niet-klasse, niet-array-type is, is de set M (X) leeg.
  [Opmerking: M (X) is de verzameling van de typen van alle niet-basisklasse subobjecten die in een standaardlay-outklasse zijn gegarandeerd om op een nulpuntverschuiving in X te staan. - einde notitie]
  [Voorbeeld:

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
  -end voorbeeld]
  108) Dit zorgt ervoor dat twee subobjecten van hetzelfde klassetype die tot hetzelfde meest afgeleide object behoren, niet op hetzelfde adres worden toegewezen.

Veranderingen:

  1. Verduidelijkt dat de vereiste dat alleen één klasse in de afleidingsboom 'niet-statische gegevensleden' heeft, verwijst naar een klasse waarin dergelijke gegevensleden voor het eerst worden gedeclareerd, en niet naar klassen waarin deze kunnen worden overgenomen, en breidde deze vereiste uit tot niet-statische bitvelden . Ook verduidelijkt dat een standaardlay-outklasse "ten hoogste één basisklasse-subobject van een bepaald type heeft". Zien DR1813, DR1881
  2. De definitie van standaardlay-out heeft het type van elke basisklasse nooit toegestaan ​​hetzelfde type te zijn als het eerste niet-statische gegevenslid. Het is om een ​​situatie te vermijden waarbij een gegevenslid op offset nul hetzelfde type heeft als elke basisklasse. De C ++ 17-standaard biedt een meer rigoureuze, recursieve definitie van "de verzameling van de typen van alle niet-basisklasse subobjecten die in een standaardlay-outklasse zijn gegarandeerd op een nulpuntsverschuiving" om dergelijke typen te verbieden van het type van elke basisklasse zijn. Zien DR1672, DR2120.

5