Co to są agregaty i POD i jak / dlaczego są wyjątkowe?


548

To FAQ dotyczy agregatów i POD i obejmuje następujące materiały:

  • Co to są agregaty ?
  • Co to są POD (zwykłe stare dane)?
  • W jaki sposób są one powiązane?
  • Jak i dlaczego są wyjątkowe?
  • Jakie zmiany w C ++ 11?


Czy można powiedzieć, że motywy tych definicji są z grubsza: POD == memcpy'able, Aggregate == agregat-inicjalizujący?
Ofek Shilon

Odpowiedzi:


571

Jak to czytac:

Ten artykuł jest raczej długi. Jeśli chcesz wiedzieć zarówno o agregatach, jak i czujnikach POD (Plain Old Data), poświęć trochę czasu i przeczytaj je. Jeśli interesują Cię tylko agregaty, przeczytaj tylko pierwszą część. Jeśli jesteś zainteresowany tylko w strąkach potem trzeba najpierw przeczytać definicji, implikacje i przykłady kruszyw a następnie mogą przejść do POD ale ja i tak polecam czytać pierwszą część w całości. Pojęcie agregatów jest niezbędne do definiowania POD. Jeśli znajdziesz jakieś błędy (nawet drobne, w tym gramatykę, stylistykę, formatowanie, składnię itp.), Zostaw komentarz, będę edytować.

Ta odpowiedź dotyczy C ++ 03. Inne standardy C ++ patrz:

Co to są agregaty i dlaczego są wyjątkowe

Formalna definicja ze standardu C ++ ( C ++ 03 8.5.1 § 1 ) :

Agregacja to tablica lub klasa (klauzula 9) bez konstruktorów zadeklarowanych przez użytkownika (12.1), bez prywatnych lub chronionych niestatycznych elementów danych (klauzula 11), bez klas podstawowych (klauzula 10) i bez funkcji wirtualnych (10.3 ).

OK, przeanalizujmy tę definicję. Przede wszystkim każda tablica jest agregacją. Klasa może być również agregacją, jeśli… czekaj! nic nie mówi się o strukturach lub związkach, czy nie mogą być agregacjami? Tak, moga. W C ++ termin classodnosi się do wszystkich klas, struktur i związków. Tak więc klasa (lub struct lub union) jest agregatem tylko wtedy, gdy spełnia kryteria z powyższych definicji. Co oznaczają te kryteria?

  • Nie oznacza to, że klasa agregująca nie może mieć konstruktorów, w rzeczywistości może mieć domyślny konstruktor i / lub konstruktor kopii, o ile są one domyślnie zadeklarowane przez kompilator, a nie jawnie przez użytkownika

  • Brak prywatnych lub chronionych niestatycznych elementów danych . Możesz mieć dowolną liczbę prywatnych i chronionych funkcji składowych (ale nie konstruktorów), a także dowolną liczbę prywatnych lub chronionych statycznych elementów danych i funkcji składowych, jak chcesz i nie naruszać reguł dla klas agregujących

  • Klasa zagregowana może mieć zadeklarowany / zdefiniowany przez użytkownika operator przypisania kopii i / lub destruktor

  • Tablica jest agregatem, nawet jeśli jest tablicą typu klasy innej niż agregat.

Teraz spójrzmy na kilka przykładów:

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

Masz pomysł. Zobaczmy teraz, jak agregaty są wyjątkowe. W przeciwieństwie do klas niezagregowanych można je inicjować za pomocą nawiasów klamrowych {}. Ta składnia inicjalizacji jest powszechnie znana dla tablic i właśnie dowiedzieliśmy się, że są to agregaty. Zacznijmy od nich.

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

if (m == n)
i- ty element tablicy jest inicjalizowany za pomocą i
else if (m <n)
pierwsze m elementów tablicy jest inicjalizowanych przez 1 , 2 ,…, mi pozostałen - melementy są, jeśli to możliwe, inicjowane wartością (wyjaśnienie terminu znajduje się poniżej)
, w przeciwnym razie, jeśli (m> n)
kompilator wygeneruje błąd w
innym przypadku (tak jest w przypadku, gdy n nie jest określone w ogóle int a[] = {1, 2, 3};)
rozmiar przyjmuje się, że tablica (n) jest równa m, więcint a[] = {1, 2, 3};jest równoważna zint a[3] = {1, 2, 3};

Gdy obiekt typu skalarną ( bool, int, char, double, wskaźniki, itp) jest wartość zainicjowany oznacza to, że jest inicjowana 0dla tego typu ( falseo bool, 0.0o double, etc.). Kiedy obiekt typu klasy z deklarowanym przez użytkownika domyślnym konstruktorem jest inicjowany wartością, wywoływany jest domyślny konstruktor. Jeśli domyślny konstruktor jest domyślnie zdefiniowany, wówczas wszystkie elementy niestatyczne są inicjowane rekurencyjnie pod względem wartości. Ta definicja jest nieprecyzyjna i nieco niepoprawna, ale powinna dać ci podstawowy pomysł. Nie można zainicjować wartości referencyjnej. Inicjalizacja wartości dla klasy niezagregowanej może się nie powieść, jeśli na przykład klasa nie ma odpowiedniego domyślnego konstruktora.

Przykłady inicjalizacji tablicy:

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
}

Zobaczmy teraz, jak można zainicjować klasy agregujące za pomocą nawiasów klamrowych. Prawie w ten sam sposób. Zamiast elementów tablicy zainicjalizujemy niestatyczne elementy danych w kolejności ich pojawienia się w definicji klasy (wszystkie są z definicji publiczne). Jeśli jest mniej inicjatorów niż członków, reszta jest inicjowana wartością. Jeśli nie można zainicjować wartości jednego z elementów, które nie zostały jawnie zainicjowane, pojawia się błąd czasu kompilacji. Jeśli jest więcej inicjatorów, niż to konieczne, otrzymujemy również błąd czasu kompilacji.

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

W powyższym przykładzie y.cjest inicjowana 'a', y.x.i1z 10, y.x.i2z 20, y.i[0]z 20, y.i[1]z 30, a y.fma wartość zainicjowany, to znaczy inicjowany 0.0. Chroniony element statyczny dw ogóle nie jest inicjowany, ponieważ tak jest static.

Łączne związki różnią się tym, że możesz inicjować tylko ich pierwszego członka za pomocą nawiasów klamrowych. Myślę, że jeśli jesteś wystarczająco zaawansowany w C ++, aby nawet rozważyć użycie związków (ich użycie może być bardzo niebezpieczne i należy o tym ostrożnie pomyśleć), możesz sam sprawdzić zasady dotyczące związków w standardzie :).

Teraz, gdy wiemy, co jest specjalnego w agregatach, spróbujmy zrozumieć ograniczenia dotyczące klas; to znaczy, dlaczego tam są. Powinniśmy zrozumieć, że inicjalizacja członowa za pomocą nawiasów oznacza, że ​​klasa jest niczym więcej niż sumą jej elementów. Jeśli obecny jest konstruktor zdefiniowany przez użytkownika, oznacza to, że użytkownik musi wykonać dodatkową pracę, aby zainicjować elementy, dlatego inicjacja nawiasów byłaby niepoprawna. Jeśli obecne są funkcje wirtualne, oznacza to, że obiekty tej klasy mają (w większości implementacji) wskaźnik do tak zwanej vtable klasy, która jest ustawiona w konstruktorze, więc inicjacja nawiasów nie byłaby wystarczająca. Resztę ograniczeń możesz znaleźć w podobny sposób jak ćwiczenie :).

Tyle o agregatach. Teraz możemy zdefiniować bardziej rygorystyczny zestaw typów, to znaczy POD

Co to są POD i dlaczego są wyjątkowe

Formalna definicja ze standardu C ++ ( C ++ 03 9 §4 ) :

Struktura POD jest strukturą zagregowaną, która nie zawiera elementów niestatycznych typu non-POD-struct, non-POD-union (lub tablic takich typów) lub referencji i nie ma zdefiniowanego przez użytkownika operatora przypisania kopii i nie ma destruktor zdefiniowany przez użytkownika. Podobnie, związek POD jest związkiem zagregowanym, który nie ma elementów niestatycznych typu typu non-POD-struct, non-POD-union (lub tablica takich typów) lub odniesienia i nie ma operatora przypisania kopii zdefiniowanego przez użytkownika i brak niszczyciela zdefiniowanego przez użytkownika. Klasa POD to klasa będąca strukturą POD lub związkiem POD.

Wow, trudniej to przeanalizować, prawda? :) Pozostawmy związki (na tych samych podstawach, co powyżej) i sformułujmy je w nieco jaśniejszy sposób:

Klasa zagregowana jest nazywana POD, jeśli nie ma zdefiniowanego przez użytkownika operatora przypisania kopii i destruktora, a żaden z jej elementów niestatycznych nie jest klasą inną niż POD, tablicą non-POD ani referencją.

Co oznacza ta definicja? (Czy wspominałem o POD oznacza Plain Old Data ?)

  • Wszystkie klasy POD są agregatami lub, inaczej mówiąc, jeśli klasa nie jest agregacją, to na pewno nie jest POD
  • Klasy, podobnie jak struktury, mogą być POD, mimo że w obu przypadkach standardowym terminem jest POD-struct
  • Podobnie jak w przypadku agregatów, nie ma znaczenia, jakie elementy statyczne ma klasa

Przykłady:

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

Klasy POD, związki POD, typy skalarne i tablice takich typów są wspólnie nazywane typami POD.
POD są wyjątkowe na wiele sposobów. Podam tylko kilka przykładów.

  • Klasy POD są najbliższe strukturom C. W przeciwieństwie do nich POD mogą mieć funkcje składowe i dowolne elementy statyczne, ale żadne z tych dwóch nie zmienia układu pamięci obiektu. Więc jeśli chcesz napisać mniej lub bardziej przenośną bibliotekę dynamiczną, z której można korzystać z C, a nawet .NET, powinieneś spróbować, aby wszystkie wyeksportowane funkcje przyjmowały i zwracały tylko parametry typów POD.

  • Żywotność obiektów klasy innej niż POD rozpoczyna się po zakończeniu konstruktora, a kończy po zakończeniu działania destruktora. W przypadku klas POD żywotność rozpoczyna się, gdy pamięć jest zajęta, i kończy się, gdy pamięć zostanie zwolniona lub ponownie użyta.

  • W przypadku obiektów typu POD norma gwarantuje, że gdy memcpyzawartość Twojego obiektu zostanie zapisana w tablicy znaków lub w postaci znaku bez znaku, a następnie memcpyzawartość z powrotem w twoim obiekcie, obiekt zachowa swoją oryginalną wartość. Należy pamiętać, że nie ma takiej gwarancji na obiekty innych niż POD. Możesz także bezpiecznie kopiować obiekty POD za pomocą memcpy. Poniższy przykład zakłada, że ​​T jest typem 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
  • oświadczenie goto. Jak zapewne wiesz, nielegalne (kompilator powinien wydać błąd) jest przeskakiwanie za pomocą goto z miejsca, w którym jakaś zmienna nie była jeszcze w zasięgu, do punktu, w którym już jest w zasięgu. To ograniczenie ma zastosowanie tylko wtedy, gdy zmienna jest typu innego niż POD. W poniższym przykładzie f()jest źle uformowany, podczas gdy g()jest dobrze uformowany. Zauważ, że kompilator Microsoftu jest zbyt liberalny w stosunku do tej reguły - w obu przypadkach wydaje tylko ostrzeżenie.

    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;
    }
  • Gwarantujemy, że na początku obiektu POD nie będzie paddingu. Innymi słowy, jeśli pierwszy element klasy POD klasy A jest typu T, możesz bezpiecznie przejść reinterpret_castod i uzyskać wskaźnik do pierwszego elementu i odwrotnie.A*T*

Lista jest długa…

Wniosek

Ważne jest, aby zrozumieć, czym dokładnie jest POD, ponieważ wiele funkcji językowych, jak widzisz, zachowuje się dla nich inaczej.


3
Niezła odpowiedź. Komentarze: „Jeśli domyślny konstruktor jest domyślnie zdefiniowany, wówczas wszystkie elementy niestatyczne są inicjowane rekurencyjnie”. oraz „Inicjalizacja wartości dla klasy niezagregowanej może się nie powieść, jeśli na przykład klasa nie ma odpowiedniego domyślnego konstruktora”. jest niepoprawny: Inicjalizacja wartości klasy za pomocą domyślnie zadeklarowanego konstruktora domyślnego nie wymaga domyślnie zdefiniowanego konstruktora domyślnego. Zatem podane (wstawić private:odpowiednio): struct A { int const a; };wtedy A()jest dobrze sformułowane, nawet jeśli Adomyślna definicja konstruktora byłaby źle sformułowana.
Johannes Schaub - litb

4
@ Kev: Jeśli uda ci się spakować te same informacje do krótszej odpowiedzi, wszyscy chętnie ją głosujemy!
sbi

3
@Armen również pamiętaj, że możesz zrobić wiele odpowiedzi na to samo pytanie. Każda odpowiedź może zawierać część rozwiązania pytania. Chrzanić to, co zaakceptowałem, moim zdaniem :)
Johannes Schaub - litb

3
Odpowiedź jest świetna. Nadal jeszcze raz odwiedzam ten post. Przy okazji o ostrzeżeniach dla Visual Studio. „goto statement” dla kapsuły, jak już wspomniałeś, nie zna kompilatora MSVC. Ale dla instrukcji switch / case generuje błąd kompilacji. W oparciu o tę koncepcję zrobiłem tester pod-checker: stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/…
bruziuz

2
W punkcie, który zaczyna się od: „Żywotność obiektów klasy innej niż POD zaczyna się po zakończeniu konstruktora, a kończy po zakończeniu destruktera”. ostatnia część powinna zamiast tego powiedzieć „kiedy uruchomi się destruktor”.
Quokka

457

Jakie zmiany w C ++ 11?

Kruszywa

Standardowa definicja agregatu zmieniła się nieznacznie, ale nadal jest prawie taka sama:

Agregacja jest tablicą lub klasą (klauzula 9) bez konstruktorów dostarczonych przez użytkownika (12.1), bez inicjatorów nawiasowych lub równych dla niestatycznych elementów danych (9.2), bez prywatnych lub chronionych elementów niestatycznych ( Klauzula 11), brak klas podstawowych (Klauzula 10) i brak funkcji wirtualnych (10.3).

Ok, co się zmieniło?

  1. Wcześniej agregacja nie mogła zawierać konstruktorów zadeklarowanych przez użytkownika , ale teraz nie może mieć konstruktorów podanych przez użytkownika . Czy jest jakaś różnica? Tak, ponieważ teraz możesz zadeklarować konstruktory i ustawić je domyślnie :

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

    Jest to nadal agregacja, ponieważ konstruktor (lub dowolna specjalna funkcja członka), który jest domyślnie ustawiony w pierwszej deklaracji, nie jest dostarczany przez użytkownika.

  2. Teraz agregacja nie może mieć żadnych inicjatorów nawiasowych lub równych dla niestatycznych elementów danych. Co to znaczy? To dlatego, że dzięki temu nowemu standardowi możemy inicjować członków bezpośrednio w klasie w następujący sposób:

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

    Użycie tej funkcji sprawia, że ​​klasa nie jest już agregacją, ponieważ jest to w zasadzie odpowiednik dostarczenia własnego domyślnego konstruktora.

To, co jest agregatem, niewiele się zmieniło. To wciąż ten sam podstawowy pomysł, dostosowany do nowych funkcji.

Co z POD?

POD przeszedł wiele zmian. Wiele wcześniejszych zasad dotyczących POD zostało złagodzonych w tym nowym standardzie, a sposób, w jaki podano definicję w normie, został radykalnie zmieniony.

Ideą POD jest uchwycenie zasadniczo dwóch różnych właściwości:

  1. Obsługuje inicjalizację statyczną oraz
  2. Kompilacja POD w C ++ daje taki sam układ pamięci jak struktura skompilowana w C.

Z tego powodu definicja została podzielona na dwie odrębne koncepcje: klasy trywialne i klasy o standardowym układzie , ponieważ są one bardziej przydatne niż POD. Norma obecnie rzadko używa terminu POD, preferując bardziej szczegółowe pojęcia trywialne i standardowe .

Nowa definicja zasadniczo mówi, że POD jest klasą, która jest zarówno trywialna, jak i ma standardowy układ, a ta właściwość musi się rekurencyjnie przechowywać dla wszystkich niestatycznych elementów danych:

Struktura POD jest klasą niezwiązkową, która jest zarówno klasą trywialną, jak i klasą o układzie standardowym, i nie ma żadnych niestatycznych elementów danych typu non-POD struct, non-POD union (lub tablica takich typów). Podobnie, związek POD jest związkiem, który jest zarówno klasą trywialną, jak i standardową klasą układu i nie ma żadnych niestatycznych elementów danych typu non-POD struct, non-POD union (lub tablica takich typów). Klasa POD to klasa będąca strukturą POD lub związkiem POD.

Omówmy szczegółowo każdą z tych dwóch właściwości osobno.

Lekcje trywialne

Trivial to pierwsza właściwość wspomniana powyżej: trywialne klasy obsługują inicjalizację statyczną. Jeśli klasa jest trywialna do skopiowania (nadzbiór trywialnych klas), można skopiować jej reprezentację nad tym miejscem z rzeczami podobnymi memcpyi oczekiwać, że wynik będzie taki sam.

Standard definiuje trywialną klasę w następujący sposób:

Trywialnie kopiowalna klasa to klasa, która:

- nie ma nietrywialnych konstruktorów kopii (12.8),

- nie ma nietrywialnych konstruktorów ruchów (12.8),

- nie ma nietrywialnych operatorów przypisywania kopii (13.5.3, 12.8),

- nie ma nietrywialnych operatorów przypisania ruchu (13.5.3, 12.8), oraz

- ma trywialny destruktor (12.4).

Klasa trywialna to klasa, która ma trywialny domyślny konstruktor (12.1) i jest trywialna do skopiowania.

[ Uwaga: W szczególności trywialnie dająca się skopiować lub trywialna klasa nie ma funkcji wirtualnych ani wirtualnych klas bazowych. —Wskazówka ]

Czym więc są te wszystkie trywialne i nietrywialne rzeczy?

Konstruktor kopiuj / przenieś dla klasy X jest trywialny, jeśli nie został dostarczony przez użytkownika i jeśli

- klasa X nie ma funkcji wirtualnych (10.3) ani wirtualnych klas bazowych (10.1), oraz

- konstruktor wybrany do kopiowania / przenoszenia każdego podobiektu klasy bezpośredniej jest trywialny, oraz

- dla każdego niestatycznego elementu danych X typu klasy (lub jego tablicy) konstruktor wybrany do kopiowania / przenoszenia tego elementu jest trywialny;

w przeciwnym razie konstruktor kopiowania / przenoszenia nie jest trywialny.

Zasadniczo oznacza to, że konstruktor kopiowania lub przenoszenia jest trywialny, jeśli nie jest udostępniony przez użytkownika, klasa nie ma w nim nic wirtualnego, a ta właściwość zachowuje się rekurencyjnie dla wszystkich członków klasy i klasy podstawowej.

Definicja trywialnego operatora kopiowania / przenoszenia jest bardzo podobna, po prostu zastępując słowo „konstruktor” słowem „operator przypisania”.

Trywialny destruktor ma również podobną definicję, z dodatkowym ograniczeniem, że nie może być wirtualny.

Istnieje jeszcze inna podobna reguła dla trywialnych domyślnych konstruktorów, z dodatkiem, że domyślny konstruktor nie jest trywialny, jeśli klasa ma niestatyczne elementy danych z inicjatorami nawiasów klamrowych lub równości , które widzieliśmy powyżej.

Oto kilka przykładów, aby wszystko wyczyścić:

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

Standardowy układ

Układ standardowy to druga właściwość. Standard wspomina, że ​​są one przydatne do komunikowania się z innymi językami, a to dlatego, że klasa standardowego układu ma taki sam układ pamięci co odpowiednik struktury C lub unii.

Jest to kolejna właściwość, którą należy przechowywać rekurencyjnie dla członków i wszystkich klas podstawowych. Jak zwykle nie są dozwolone żadne funkcje wirtualne ani wirtualne klasy podstawowe. To spowodowałoby, że układ byłby niezgodny z C.

Zrelaksowana zasada polega na tym, że klasy o układzie standardowym muszą mieć wszystkie niestatyczne elementy danych z taką samą kontrolą dostępu. Wcześniej musiały być wszystkie publiczne , ale teraz możesz ustawić je jako prywatne lub chronione, o ile wszystkie są prywatne lub wszystkie chronione.

Podczas korzystania z dziedziczenia tylko jedna klasa w całym drzewie dziedziczenia może mieć niestatyczne elementy danych, a pierwszy niestatyczny element danych nie może być typu klasy podstawowej (może to złamać reguły aliasingu), w przeciwnym razie nie jest to standard- klasa układu.

Tak wygląda definicja w tekście standardowym:

Klasa z układem standardowym to klasa, która:

- nie ma elementów danych niestatycznych typu niestandardowego typu układu (lub tablicy takich typów) lub odniesienia,

- nie ma funkcji wirtualnych (10.3) ani wirtualnych klas bazowych (10.1),

- ma taką samą kontrolę dostępu (klauzula 11) dla wszystkich niestatycznych członków danych,

- nie ma klas bazowych o niestandardowym układzie,

- albo nie ma niestatycznych elementów danych w najbardziej pochodnej klasie i co najwyżej jednej klasy bazowej z niestatycznymi elementami danych, lub nie ma klas podstawowych z niestatycznymi elementami danych, oraz

- nie ma klas podstawowych tego samego typu, co pierwszy niestatyczny element danych.

Struktura standardowego układu to klasa standardowego układu zdefiniowana za pomocą struktury klucza klasy lub klasy klucza klasy.

Unia układu standardowego to klasa układu standardowego zdefiniowana za pomocą związku klucza klasy.

[ Uwaga: Klasy układu standardowego są przydatne do komunikacji z kodem napisanym w innych językach programowania. Ich układ jest określony w 9.2. —Wskazówka ]

Zobaczmy kilka przykładów.

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

Wniosek

Dzięki tym nowym regułom o wiele więcej typów może być teraz POD. I nawet jeśli typ nie jest POD, możemy skorzystać z niektórych właściwości POD osobno (jeśli jest to tylko trywialny lub standardowy układ).

Biblioteka standardowa ma cechy do testowania tych właściwości w nagłówku <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;

2
czy możesz opracować następujące zasady: a) klasy o standardowym układzie muszą mieć wszystkie elementy danych niestatycznych z taką samą kontrolą dostępu; b) tylko jedna klasa w całym drzewie dziedziczenia może mieć niestatyczne elementy danych, a pierwszy niestatyczny element danych nie może być typu klasy podstawowej (może to złamać reguły aliasingu). Zwłaszcza jakie są ich przyczyny? Czy w przypadku późniejszej reguły możesz podać przykład łamania aliasingu?
Andriy Tylychko,

@AndyT: Zobacz moją odpowiedź. Próbowałem odpowiedzieć zgodnie z moją najlepszą wiedzą.
Nicol Bolas

5
Być może chcę to zaktualizować dla C ++ 14, który usunął wymóg „brak nawiasów klamrowych lub inicjalizacji równości” dla agregatów.
TC

@TC dzięki za heads-up. Zaraz sprawdzę te zmiany i zaktualizuję je.
R. Martinho Fernandes

1
Odnośnie aliasingu: istnieje reguła układu C ++, że jeśli klasa C ma (pustą) podstawę X, a pierwszy element danych C jest typu X, to ten pierwszy element nie może mieć tego samego przesunięcia jak podstawa X; dostaje bajt dopełniający bajtu przed nim, jeśli to konieczne, aby tego uniknąć. Posiadanie dwóch instancji X (lub podklasy) pod tym samym adresem może uszkodzić rzeczy, które wymagają rozróżnienia różnych instancji za pomocą ich adresów (pusta instancja nie ma nic innego do odróżnienia go ...). W każdym razie potrzeba wstawienia tego bajtu wypełniania przerywa „kompatybilność układu”.
greggo

106

Co się zmieniło dla C ++ 14

Możemy odwoływać się do standardu Draft C ++ 14 w celach informacyjnych.

Kruszywa

Jest to omówione w sekcji 8.5.1 Agregaty, która podaje następującą definicję:

Agregacja to tablica lub klasa (klauzula 9) bez konstruktorów podanych przez użytkownika (12.1), bez prywatnych lub chronionych elementów niestatycznych danych (klauzula 11), bez klas podstawowych (klauzula 10) i bez funkcji wirtualnych (10.3 ).

Jedyną zmianą jest teraz dodanie inicjatorów elementów w klasie , które nie powodują, że klasa nie jest agregacją. Więc poniższym przykładzie z c ++ 11 zagregowanego inicjalizacji dla klas z członkiem w tempie-inicjalizatorów :

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

nie był agregatem w C ++ 11, ale jest w C ++ 14. Ta zmiana jest uwzględniona w N3605: Inicjatory składowe i agregaty , które mają następujące streszczenie:

Bjarne Stroustrup i Richard Smith podnieśli problem związany z inicjalizacją agregacji i niedziałaniem inicjatorów składowych. W tym artykule zaproponowano rozwiązanie tego problemu poprzez przyjęcie proponowanego sformułowania Smitha, które usuwa ograniczenie, że agregaty nie mogą mieć inicjatorów składowych.

POD pozostaje taki sam

Definicja struktury POD ( zwykłe stare dane ) została omówiona w sekcji 9 Klasy, która mówi:

Struktura POD 110 jest klasą niezwiązkową, która jest zarówno klasą trywialną, jak i klasą z układem standardowym i nie ma żadnych niestatycznych elementów danych typu non-POD struct, non-POD union (lub tablica takich typów). Podobnie, związek POD jest związkiem, który jest zarówno klasą trywialną, jak i klasą o układzie standardowym i nie ma żadnych niestatycznych elementów danych typu non-POD struct, non-POD union (lub tablica takich typów). Klasa POD to klasa będąca strukturą POD lub związkiem POD.

który jest taki sam jak C ++ 11.

Zmiany w układzie standardowym dla C ++ 14

Jak zauważono w komentarzach, pod opiera się na definicji standardowego układu, która zmieniła się dla C ++ 14, ale stało się tak dzięki raportom błędów, które zostały zastosowane do C ++ 14 po fakcie.

Były trzy DR:

Tak więc standardowy układ wyszedł z tego Pre C ++ 14:

Klasa z układem standardowym to klasa, która:

  • (7.1) nie ma elementów danych niestatycznych typu niestandardowego układu klasy (lub tablicy takich typów) lub odniesienia,
  • (7.2) nie ma funkcji wirtualnych ([class.virtual]) ani wirtualnych klas bazowych ([class.mi]),
  • (7.3) ma taką samą kontrolę dostępu (klauzula [class.access]) dla wszystkich niestatycznych elementów danych,
  • (7.4) nie ma klas bazowych o niestandardowym układzie,
  • (7.5) albo nie ma niestatycznych elementów danych w najbardziej pochodnej klasie i co najwyżej jednej klasy bazowej z niestatycznymi elementami danych, albo nie ma klas podstawowych z niestatycznymi elementami danych, i
  • (7.6) nie ma klas podstawowych tego samego typu, co pierwszy element danych niestatycznych. 109

Do tego w C ++ 14 :

Klasa S jest klasą układu standardowego, jeżeli:

  • (3.1) nie ma elementów danych niestatycznych typu niestandardowego układu klasy (lub tablicy takich typów) lub odniesienia,
  • (3.2) nie ma wirtualnych funkcji i wirtualnych klas bazowych,
  • (3.3) ma taką samą kontrolę dostępu dla wszystkich niestatycznych elementów danych,
  • (3.4) nie ma klas bazowych o niestandardowym układzie,
  • (3.5) ma co najwyżej jeden podobiekt klasy bazowej dowolnego danego typu,
  • (3.6) ma wszystkie niestatyczne elementy danych i pola bitowe w klasie, a jej klasy podstawowe najpierw zadeklarowane w tej samej klasie, oraz
  • (3.7) nie ma elementu zestawu M (S) typów jako klasy bazowej, gdzie dla dowolnego typu X, M (X) jest zdefiniowany w następujący sposób.104 [Uwaga: M (X) jest zbiorem typów wszystkie podobiekty klasy innej niż podstawowa, które mogą mieć przesunięcie zerowe w X. - uwaga końcowa]
    • (3.7.1) Jeśli X jest niekluczowym typem klasy bez żadnych (prawdopodobnie odziedziczonych) elementów niestatycznych danych, zestaw M (X) jest pusty.
    • (3.7.2) Jeżeli X jest niekluczowym typem klasy z niestatycznym składnikiem danych typu X0, który ma albo zerowy rozmiar, albo jest pierwszym niestatycznym składnikiem danych X (przy czym ten człon może być anonimowym związkiem ), zestaw M (X) składa się z X0 i elementów M (X0).
    • (3.7.3) Jeśli X jest typem unii, zbiór M (X) jest sumą wszystkich M (Ui) i zbiorem zawierającym wszystkie Ui, gdzie każdy Ui jest typem i-tego niestatycznego elementu danych X .
    • (3.7.4) Jeśli X jest typem tablicy z elementem typu Xe, zbiór M (X) składa się z Xe i elementów M (Xe).
    • (3.7.5) Jeśli X jest nieklasowym typem niebędącym tablicą, zestaw M (X) jest pusty.

4
Proponuje się zezwolenie, aby agregaty miały klasę podstawową, o ile jest ona domyślnie możliwa do
Shafik

podczas gdy POD może pozostać bez zmian, C ++ 14 StandardLayoutType, który jest wymagany dla POD, zmienił się zgodnie z cppref: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

1
@CiroSantilli 法轮功 改造 中心 六四 事件 法轮功 dziękuję, nie wiem, jak to przeoczyłem, postaram się zaktualizować w ciągu następnych kilku dni.
Shafik Yaghmour,

Daj mi znać, czy możesz wymyślić przykład, który jest POD w C ++ 14, ale nie w C ++ 11 :-) Rozpocząłem szczegółową listę przykładów na: stackoverflow.com/questions/146452/what- are-pod-types-in-c /…
Ciro Santilli 25 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功, więc co się tutaj wydarzyło, jeśli spojrzymy na opis standardowego układu w C ++ 11 i C ++ 14 pasują do siebie. Zmiany te zostały zastosowane za pośrednictwem raportów błędów z powrotem do C ++ 14. Więc kiedy napisałem to pierwotnie, było to poprawne :-p
Shafik

47

czy możesz opracować następujące zasady:

Spróbuję:

a) klasy o układzie standardowym muszą mieć wszystkie elementy danych niestatycznych z taką samą kontrolą dostępu

To proste: wszystkie non-statyczne członkowie danych musi wszystko być public, privatealbo protected. Nie możesz mieć trochępublic niektórych private.

Ich uzasadnienie odnosi się do rozumowania, że ​​w ogóle rozróżnia się „układ standardowy” i „układ niestandardowy”. Mianowicie, aby dać kompilatorowi swobodę wyboru sposobu zapisania rzeczy w pamięci. Nie chodzi tylko o wskaźniki vtable.

Kiedy znormalizowali C ++ w 98, musieli w zasadzie przewidzieć, w jaki sposób ludzie będą go implementować. Chociaż mieli sporo doświadczenia w implementacji różnych wersji C ++, nie byli pewni co do rzeczy. Postanowili więc zachować ostrożność: dać kompilatorom jak najwięcej swobody.

Dlatego definicja POD w C ++ 98 jest tak ścisła. Dało to kompilatorom C ++ dużą swobodę w zakresie układu elementów dla większości klas. Zasadniczo typy POD miały być szczególnymi przypadkami, czymś specjalnie napisanym z konkretnego powodu.

Podczas pracy nad C ++ 11 mieli dużo więcej doświadczenia z kompilatorami. I zdali sobie sprawę, że ... autorzy kompilatorów C ++ są naprawdę leniwi. Mieli całą tę wolność, ale nic z tym nie zrobili .

Reguły standardowego układu są mniej więcej kodyfikującą powszechną praktyką: większość kompilatorów tak naprawdę wcale nie musiała dużo zmieniać, aby cokolwiek zaimplementować (poza pewnymi rzeczami dla odpowiednich cech typu).

Teraz, jeśli chodzi o public/ private, rzeczy są inne. Swoboda zmiany kolejności członków, którzy są publicvs.private rzeczywistymi może mieć znaczenie dla kompilatora, szczególnie przy debugowaniu kompilacji. A ponieważ standardowym układem jest kompatybilność z innymi językami, nie można mieć innego układu w debugowaniu niż w wydaniu.

Jest też fakt, że tak naprawdę nie zaszkodzi to użytkownikowi. Jeśli tworzysz klasę zamkniętą, szanse są duże, że i tak wszyscy twoi członkowie danych będą private. Zasadniczo nie ujawniasz publicznych członków danych w pełni enkapsulowanych typach. Byłby to więc problem tylko dla tych nielicznych użytkowników, którzy chcą to zrobić i chcą tego podziału.

Więc to nie jest wielka strata.

b) tylko jedna klasa w całym drzewie dziedziczenia może mieć niestatycznych członków danych,

Powodem tego jest to, dlaczego ponownie znormalizowali standardowy układ: powszechna praktyka.

Nie ma powszechnej praktyki, jeśli chodzi o posiadanie dwóch członków drzewa spadkowego, które faktycznie przechowują rzeczy. Niektórzy stawiają klasę podstawową przed pochodną, ​​inni robią to w drugą stronę. W jaki sposób zamawiasz członków, jeśli pochodzą z dwóch klas podstawowych? I tak dalej. Kompilatory różnią się znacznie w tych kwestiach.

Ponadto, dzięki zasadzie zero / one / infinity, kiedy powiesz, że możesz mieć dwie klasy z członkami, możesz powiedzieć tyle, ile chcesz. Wymaga to dodania wielu reguł układu, jak sobie z tym poradzić. Musisz powiedzieć, jak działa wielokrotne dziedziczenie, które klasy umieszczają swoje dane przed innymi klasami itp. To wiele zasad, przy bardzo niewielkim zysku materialnym.

Nie możesz zrobić wszystkiego, co nie ma funkcji wirtualnych i domyślnego standardowego układu konstruktora.

a pierwszy niestatyczny element danych nie może być typu klasy podstawowej (może to złamać reguły aliasingu).

Naprawdę nie mogę z tym rozmawiać. Nie jestem wystarczająco wykształcony w zasadach aliasingu C ++, aby naprawdę to zrozumieć. Ma to jednak coś wspólnego z faktem, że element podstawowy będzie miał ten sam adres co sama klasa podstawowa. To jest:

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

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

I prawdopodobnie jest to sprzeczne z zasadami aliasingu C ++. W pewnym sensie.

Uważa jednak, że w ten sposób: jak użyteczne mogłoby posiadające zdolność, aby to zrobić kiedykolwiek rzeczywiście było? Ponieważ tylko jedna klasa może mieć niestatyczne elementy danych, Derivedmusi to być ta klasa (ponieważ ma ona Basejako element członkowski). Więc Base musi być pusty (danych). A jeśli Basejest pusty, a także klasa podstawowa ... dlaczego w ogóle ma go członek danych?

Ponieważ Basejest pusty, nie ma stanu. Tak więc wszelkie niestatyczne funkcje składowe wykonają to, co robią na podstawie swoich parametrów, a nie thiswskaźnika.

Więc znowu: bez dużej straty.


Dzięki za wyjaśnienie, to bardzo pomaga. Prawdopodobnie pomimo static_cast<Base*>(&d)i &d.bsą tego samego Base*typu, wskazują na różne rzeczy, łamiąc w ten sposób zasadę aliasingu. Proszę mnie poprawić.
Andrij Tylychko

1
i dlaczego, jeśli tylko jedna klasa może mieć niestatyczne elementy danych, to Derivedmusi to być ta klasa?
Andriy Tylychko

3
@AndyT: Aby Derivedpierwszy element członkowski był klasą podstawową, musi mieć dwie rzeczy: klasę podstawową i element członkowski . A ponieważ tylko jedna klasa w hierarchii może mieć elementy (i nadal mieć układ standardowy), oznacza to, że jej klasa podstawowa nie może mieć elementów.
Nicol Bolas

3
@AndyT, tak, w zasadzie masz rację, IME, odnośnie zasady aliasingu. Wymagane są dwa różne wystąpienia tego samego typu, aby mieć odrębne adresy pamięci. (Pozwala to na śledzenie tożsamości obiektu z adresami pamięci.) Obiekt podstawowy i pierwszy element pochodny są różnymi instancjami, więc muszą mieć różne adresy, co wymusza dodanie dopełnienia, wpływając na układ klasy. Gdyby były różnego rodzaju, nie miałoby to znaczenia; obiekty różnych typów mogą mieć ten sam adres (na przykład klasa i jej pierwszy element danych).
Adam H. Peterson

46

Zmiany w C ++ 17

Pobierz końcowy projekt standardu międzynarodowego C ++ 17 International tutaj .

Kruszywa

C ++ 17 rozszerza i usprawnia agregowanie i inicjalizację agregatów. Biblioteka standardowa zawiera teraz także std::is_aggregateklasę cech typu. Oto formalna definicja z sekcji 11.6.1.1 i 11.6.1.2 (wybrane odniesienia wewnętrzne):

Agregacja to tablica lub klasa
bez - dostarczonych przez użytkownika, jawnych lub odziedziczonych konstruktorów,
- bez prywatnych lub chronionych niestatycznych elementów danych,
- bez funkcji wirtualnych i
- bez wirtualnych, prywatnych lub chronionych klas podstawowych.
[Uwaga: Inicjalizacja zagregowana nie pozwala na dostęp do chronionych i prywatnych członków lub konstruktorów klasy podstawowej. —Wskazówka]
Elementami agregacji są:
- dla tablicy elementy tablicy w porządku rosnącym indeksu dolnego lub
- dla klasy bezpośrednie klasy podstawowe w kolejności deklaracji, po których następują bezpośrednie niestatyczne elementy danych, które nie są członkowie anonimowego związku w kolejności deklaracji.

Co się zmieniło?

  1. Agregaty mogą teraz mieć publiczne, nie wirtualne klasy podstawowe. Ponadto nie jest wymagane, aby klasy podstawowe były agregatami. Jeśli nie są agregacjami, są inicjowane na liście.
struct B1 // not a aggregate
{
    int i1;
    B1(int a) : i1(a) { }
};
struct B2
{
    int i2;
    B2() = default;
};
struct M // not an aggregate
{
    int m;
    M(int a) : m(a) { }
};
struct C : B1, B2
{
    int j;
    M m;
    C() = default;
};
C c { { 1 }, { 2 }, 3, { 4 } };
cout
    << "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. Jawne domyślne konstruktory są niedozwolone
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Konstruktory dziedziczące są niedozwolone
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Trywialne klasy

Definicja klasy trywialnej została przerobiona w C ++ 17, aby rozwiązać kilka błędów, które nie zostały usunięte w C ++ 14. Zmiany miały charakter techniczny. Oto nowa definicja w wersji 12.0.6 (z odniesieniem do wewnętrznych odniesień):

Trywialnie kopiowalna klasa to klasa:
- w której każdy konstruktor kopiujący, konstruktor przenoszenia, operator przypisania kopii i operator przypisania przeniesienia jest albo usunięty, albo trywialny,
- który ma co najmniej jeden nieusunięty konstruktor kopii, konstruktor przeniesienia, operator przypisania kopii, lub przenieść operator przypisania, i
- który ma trywialny, nieskasowany destruktor.
Klasa trywialna to klasa, którą można w trywialny sposób kopiować i ma jeden lub więcej domyślnych konstruktorów, z których wszystkie są albo trywialne, albo usunięte, a przynajmniej jedna z nich nie jest usuwana. [Uwaga: W szczególności klasa, którą można w prosty sposób kopiować lub trywialna, nie ma funkcji wirtualnych ani wirtualnych klas bazowych. - uwaga końcowa]

Zmiany:

  1. W C ++ 14, aby klasa była trywialna, klasa nie mogła mieć żadnych konstruktorów / operatorów przypisania, które nie byłyby trywialne. Jednak wówczas domyślnie zadeklarowany jako domyślny konstruktor / operator może być trywialny, a mimo to zdefiniowany jako usunięty, ponieważ na przykład klasa zawiera podobiekt typu klasy, którego nie można skopiować / przenieść. Obecność takiego nietrywialnego konstruktora / operatora zdefiniowanego jako usunięty spowodowałby, że cała klasa byłaby nietrywialna. Podobny problem istniał z destruktorami. C ++ 17 wyjaśnia, że ​​obecność takiego konstruktora / operatorów nie powoduje, że klasa nie jest trywialna do skopiowania, a zatem nie jest trywialna, oraz że klasa trywialnie kopiowalna musi mieć trywialny, nieskasowany destruktor. DR1734 , DR1928
  2. C ++ 14 zezwalał na klasę, która może być trywialnie kopiowana, a więc na klasę trywialną, aby każdy konstruktor / operator przypisania kopiowania został zadeklarowany jako usunięty. Gdyby klasa taka była również standardowym układem, mogłaby być jednak legalnie kopiowana / przenoszona std::memcpy. Była to sprzeczność semantyczna, ponieważ definiując jako usunięte wszystkie operatory konstruktora / przypisania, twórca klasy wyraźnie chciał, aby klasa nie mogła być kopiowana / przenoszona, ale klasa nadal spełniała definicję klasy, którą można w prosty sposób skopiować. Dlatego w C ++ 17 mamy nową klauzulę stwierdzającą, że trywialnie dająca się skopiować klasa musi mieć co najmniej jeden trywialny, nieskasowany (choć niekoniecznie publicznie dostępny) konstruktor / operator przenoszenia / przypisania. Patrz N4148 , DR1734
  3. Trzecia zmiana techniczna dotyczy podobnego problemu z domyślnymi konstruktorami. W C ++ 14 klasa może mieć trywialne domyślne konstruktory, które zostały domyślnie zdefiniowane jako usunięte, ale nadal mogą być trywialną klasą. Nowa definicja wyjaśnia, że ​​klasa trywialna musi mieć co najmniej jeden trywialny, nieskasowany domyślny konstruktor. Zobacz DR1496

Klasy w układzie standardowym

Zmodyfikowano także definicję standardowego układu, aby uwzględnić raporty błędów. Ponownie zmiany miały charakter techniczny. Oto tekst ze standardu (12.0.7). Tak jak poprzednio, wybierane są odniesienia wewnętrzne:

Klasa S klasy standardowego układu, jeżeli:
- nie ma nie-statycznych pól grupy typu nie-standardowego układu (lub szeregu takich typów) lub odniesienia,
- nie ma funkcji wirtualnych i nie klas wirtualnych bazowe
- ma taką samą kontrolę dostępu dla wszystkich niestatycznych elementów danych,
- nie ma klas bazowych o niestandardowym układzie,
- ma co najwyżej jeden podobiekt klasy bazowej dowolnego określonego typu,
- ma wszystkie niestatyczne elementy danych i pola bitowe w klasa i jej klasy podstawowe zadeklarowane po raz pierwszy w tej samej klasie, i
- nie ma elementu zestawu M (S) typów (zdefiniowanych poniżej) jako klasa podstawowa. 108
M (X) jest zdefiniowana w następujący sposób:
- Jeśli X jest niekluczowym typem klasy bez żadnych (prawdopodobnie odziedziczonych) elementów niestatycznych danych, zestaw M (X) jest pusty.
- Jeśli X jest typem klasy nie-unii, której pierwszy niestatyczny element danych ma typ X0 (gdzie ten element może być anonimowym związkiem), zestaw M (X) składa się z X0 i elementów M (X0).
- Jeśli X jest typem unii, zbiór M (X) jest sumą wszystkich M (Ui) i zestawem zawierającym wszystkie Ui, gdzie każdy Ui jest typem i-tego niestatycznego elementu danych X.
- Jeśli X jest typem tablicy z typem elementu Xe, zestaw M (X) składa się z Xe i elementów M (Xe).
- Jeśli X jest nieklasowym typem niebędącym tablicą, zestaw M (X) jest pusty.
[Uwaga: M (X) jest zbiorem typów wszystkich podobiektów spoza klasy podstawowej, które są gwarantowane w klasie układu standardowego z zerowym przesunięciem w X. - uwaga końcowa]
[Przykład:

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
—Przykład]
108) Zapewnia to, że dwa podobiekty, które mają ten sam typ klasy i należą do tego samego najbardziej pochodnego obiektu, nie są przydzielane pod tym samym adresem.

Zmiany:

  1. Wyjaśniono, że wymóg, zgodnie z którym tylko jedna klasa w drzewie pochodnym „ma” elementy danych niestatycznych, odnosi się do klasy, w której takie elementy danych są najpierw deklarowane, a nie klas, w których można je odziedziczyć, i rozszerzyły to wymaganie na niestatyczne pola bitowe . Wyjaśniono również, że klasa z układem standardowym „ma co najwyżej jeden podobiekt klasy bazowej dowolnego danego typu”. Patrz DR1813 , DR1881
  2. Definicja układu standardowego nigdy nie pozwalała, aby typ jakiejkolwiek klasy bazowej był tego samego typu, co pierwszy niestatyczny element danych. Ma to na celu uniknięcie sytuacji, w której element danych z przesunięciem zera ma ten sam typ, co dowolna klasa bazowa. Standard C ++ 17 zapewnia bardziej rygorystyczną, rekurencyjną definicję „zestawu typów wszystkich podobiektów nie należących do klasy podstawowej, które są gwarantowane w klasie układu standardowego z zerowym przesunięciem”, aby zabronić takich typów od bycia typem dowolnej klasy bazowej. Patrz DR1672 , DR2120 .

Uwaga: Komitet ds. Standardów C ++ zamierzał zastosować powyższe zmiany w oparciu o raporty błędów do C ++ 14, chociaż nowego języka nie ma w opublikowanym standardzie C ++ 14. Jest w standardzie C ++ 17.


Uwaga Właśnie zaktualizowałem swoją odpowiedź . Standardowe zmiany w układzie defekty mają status CD4, co oznacza, że ​​są one faktycznie stosowane w C ++ 14. Właśnie dlatego moja odpowiedź nie zawierała ich b / c, co zdarzyło się po napisaniu mojej odpowiedzi.
Shafik Yaghmour,

Uwaga, rozpocząłem nagrodę za to pytanie.
Shafik Yaghmour,

Dzięki @ShafikYaghmour. Sprawdzę status raportów wad i odpowiednio zmodyfikuję swoją odpowiedź.
ThomasMcLeod,

@ShafikYaghmour, Po krótkiej analizie procesu C ++ 14 wydaje mi się, że podczas gdy te DR zostały „zaakceptowane” na spotkaniu Rapperswil w czerwcu 2014 r., Język z spotkania Issaquah w lutym 2014 r. Stał się C ++ 14. Zobacz isocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting „zgodnie z regułami ISO formalnie nie zatwierdziliśmy żadnych zmian w dokumencie roboczym C ++”. Czy coś brakuje?
ThomasMcLeod,

Mają status „CD4”, co oznacza, że ​​powinny być stosowane w trybie C ++ 14.
Shafik Yaghmour

14

Co się zmienia

Zgodnie z resztą jasnego tematu tego pytania, znaczenie i zastosowanie agregatów zmienia się z każdym standardem. Istnieje kilka kluczowych zmian na horyzoncie.

Typy z deklarowanymi przez użytkownika konstruktorami P1008

W C ++ 17 ten typ jest nadal agregacją:

struct X {
    X() = delete;
};

A zatem X{}nadal się kompiluje, ponieważ jest to inicjalizacja zbiorcza, a nie wywołanie konstruktora. Zobacz także: Kiedy prywatny konstruktor nie jest prywatnym konstruktorem?

W C ++ 20 ograniczenie zmieni się z wymagania:

brak podanych przez użytkownika explicitlub odziedziczonych konstruktorów

do

brak deklarowanych lub dziedziczonych konstruktorów

Zostało to przyjęte w roboczym projekcie C ++ 20 . Ani Xtutaj, ani Cw powiązanym pytaniu nie będą agregowane w C ++ 20.

Daje to również efekt jo-jo w następującym przykładzie:

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

W C ++ 11/14, Bbyła nie agregat ze względu na klasy bazowej, więc B{}wykonuje wartości inicjalizacji, który wzywa B::B(), który wzywa A::A(), w miejscu, gdzie jest ona dostępna. To było dobrze uformowane.

W C ++ 17 Bstał się agregatorem, ponieważ dozwolone były klasy podstawowe, co spowodowało B{}inicjalizację agregacji. Wymaga to inicjalizacji listy kopii Az {}, ale spoza kontekstu B, w którym nie jest dostępny. W C ++ 17 jest to źle sformułowane ( auto x = B();byłoby dobrze).

W C ++ 20 teraz, z powodu powyższej zmiany reguły, Bpo raz kolejny przestaje być agregacją (nie z powodu klasy podstawowej, ale z powodu deklarowanego przez użytkownika domyślnego konstruktora - nawet jeśli jest on domyślny). Wracamy do Bkonstruktora i ten fragment kodu jest dobrze uformowany.

Inicjowanie agregatów z nawiasowej listy wartości P960

Często pojawiającym się problemem jest konieczność użycia emplace()konstruktorów w stylu z agregacjami:

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

To nie działa, ponieważ emplacespróbuje skutecznie wykonać inicjalizację X(1, 2), co jest nieprawidłowe. Typowym rozwiązaniem jest dodanie konstruktora X, ale dzięki tej propozycji (obecnie działającej w Core) agregaty będą skutecznie syntetyzowały konstruktory, które postępują właściwie i zachowują się jak zwykłe konstruktory. Powyższy kod zostanie skompilowany w stanie obecnym w C ++ 20.

Odliczenie argumentu szablonu klasy (CTAD) dla agregatów P1021 (konkretnie P1816 )

W C ++ 17 nie kompiluje się:

template <typename T>
struct Point {
    T x, y;
};

Point p{1, 2}; // error

Użytkownicy musieliby napisać własny przewodnik po dedukcji dla wszystkich szablonów agregujących:

template <typename T> Point(T, T) -> Point<T>;

Ale ponieważ jest to w pewnym sensie „oczywista rzecz” do zrobienia i jest to po prostu bojler, język zrobi to za Ciebie. Ten przykład skompiluje się w C ++ 20 (bez potrzeby dostarczonego przez użytkownika przewodnika po dedukcji).


Chociaż będę głosować, dodawanie tego jest trochę za wcześnie, nie wiem jednak o żadnym poważnym zejściu, który zmieniłby to przed ukończeniem C ++ 2x.
Shafik Yaghmour,

@ShafikYaghmour Tak, prawdopodobnie ZBYT zbyt wcześnie. Ale biorąc pod uwagę, że SD był ostatecznym terminem dla nowych funkcji językowych, są to jedyne dwa w locie, o których jestem świadomy - najgorszy przypadek, że po prostu zablokowałem jedną z tych sekcji później? Właśnie zobaczyłem pytanie aktywne z nagrodą i pomyślałem, że nadszedł dobry czas, aby się w niego włączyć, zanim zapomnę.
Barry

Rozumiem, kilka razy kusiło mnie podobne przypadki. Zawsze martwię się, że coś ważnego się zmieni i w końcu będę musiał to przepisać.
Shafik Yaghmour,

@ShafikYaghmour Wydaje się, że nic się tutaj nie zmieni :)
Barry

Mam nadzieję, że zostanie to teraz zaktualizowane,
ponieważ
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.