Udostępnij przez


Typy trivial, standard-layout, POD i literałów

Termin układ odnosi się do sposobu rozmieszczania składowych obiektu klasy, struktury lub typu unii w pamięci. W niektórych przypadkach układ jest dobrze zdefiniowany przez specyfikację języka. Jeśli jednak klasa lub struktura zawiera pewne funkcje języka C++, takie jak wirtualne klasy bazowe, funkcje wirtualne, elementy członkowskie z inną kontrolą dostępu, kompilator może wybrać układ. Ten układ może się różnić w zależności od tego, jakie optymalizacje są wykonywane, a w wielu przypadkach obiekt może nawet nie zajmować ciągłego obszaru pamięci. Jeśli na przykład klasa ma funkcje wirtualne, wszystkie wystąpienia tej klasy mogą współdzielić jedną tabelę funkcji wirtualnych. Takie typy są bardzo przydatne, ale mają również ograniczenia. Ponieważ układ jest niezdefiniowany, nie można przekazać ich do programów napisanych w innych językach, takich jak C, i ponieważ mogą być nieciągłe, nie mogą być niezawodnie kopiowane za pomocą szybkich funkcji niskiego poziomu, takich jak memcopy, lub serializowane przez sieć.

Aby umożliwić kompilatorom, a także programom c++ i metaprogramom przyczynę przydatności dowolnego typu dla operacji, które zależą od określonego układu pamięci, język C++14 wprowadził trzy kategorie prostych klas i struktur: trywialny, układ standardowy i zasobnik lub zwykłe stare dane. Biblioteka Standardowa zawiera szablony is_trivial<T>funkcji i is_pod<T> określają, is_standard_layout<T> czy dany typ należy do danej kategorii.

Typy trywialne

Gdy klasa lub struktura w języku C++ ma udostępnione kompilator lub jawnie domyślne specjalne funkcje składowe, jest to typ trywialny. Zajmuje ciągły obszar pamięci. Może mieć członków z różnymi specyfikatorami dostępu. W języku C++kompilator może wybrać sposób zamawiania elementów członkowskich w tej sytuacji. W związku z tym można memcopy takich obiektów, ale nie można ich niezawodnie używać z poziomu programu C. Typ trywialny T można skopiować do tablicy znaków lub niepodpisanego znaku i bezpiecznie skopiować z powrotem do zmiennej T. Należy pamiętać, że ze względu na wymagania dotyczące wyrównania mogą istnieć dopełnianie bajtów między elementami członkowskimi typu.

Typy trywialne mają trywialny konstruktor domyślny, trywialny konstruktor kopiowania, trywialny operator przypisania kopiowania i trywialny destruktor. W każdym przypadku trywialny oznacza, że konstruktor/operator/destruktor nie jest dostarczany przez użytkownika i należy do klasy, która ma

  • brak funkcji wirtualnych ani wirtualnych klas bazowych,

  • brak klas bazowych z odpowiadającym nietrywialnym konstruktorem/operatorem/destruktorem

  • brak składowych danych typu klasy z odpowiadającym nietrywialnym konstruktorem/operatorem/destruktorem

W poniższych przykładach przedstawiono proste typy. W języku Trivial2 obecność Trivial2(int a, int b) konstruktora wymaga podania konstruktora domyślnego. Aby typ kwalifikował się jako trywialny, należy jawnie ustawić ten konstruktor jako domyślny.

struct Trivial
{
   int i;
private:
   int j;
};

struct Trivial2
{
   int i;
   Trivial2(int a, int b) : i(a), j(b) {}
   Trivial2() = default;
private:
   int j;   // Different access control
};

Standardowe typy układów

Jeśli klasa lub struktura nie zawiera pewnych funkcji języka C++, takich jak funkcje wirtualne, które nie znajdują się w języku C, a wszystkie elementy członkowskie mają taką samą kontrolę dostępu, jest to typ układu standardowego. Jest on w stanie memcopy i układ jest wystarczająco zdefiniowany, że może być używany przez programy języka C. Typy układów standardowych mogą mieć specjalne funkcje składowe zdefiniowane przez użytkownika. Ponadto standardowe typy układów mają następujące cechy:

  • brak funkcji wirtualnych ani wirtualnych klas bazowych

  • wszystkie niestatyczne elementy członkowskie danych mają taką samą kontrolę dostępu

  • wszystkie niestatyczne składowe typu klasy są układem standardowym

  • wszystkie klasy bazowe są układem standardowym

  • nie ma klas bazowych tego samego typu co pierwszy niestatyczny element członkowski danych.

  • spełnia jeden z następujących warunków:

    • brak statycznej składowej danych w klasie najbardziej pochodnej i nie więcej niż jedna klasa bazowa z niestacjonowymi elementami członkowskimi danych lub

    • nie ma klas bazowych z niestacjonowymi elementami członkowskimi danych

Poniższy kod przedstawia jeden przykład standardowego typu układu:

struct SL
{
   // All members have same access:
   int i;
   int j;
   SL(int a, int b) : i(a), j(b) {} // User-defined constructor OK
};

Ostatnie dwa wymagania mogą być prawdopodobnie lepiej zilustrowane przy użyciu kodu. W następnym przykładzie, mimo że baza jest układem standardowym, nie jest układem standardowym, Derived ponieważ oba te elementy (najbardziej pochodna klasa) i Base mają niestatyczne składowe danych:

struct Base
{
   int i;
   int j;
};

// std::is_standard_layout<Derived> == false!
struct Derived : public Base
{
   int x;
   int y;
};

W tym przykładzie Derived jest układ standardowy, ponieważ Base nie ma statycznych elementów członkowskich danych:

struct Base
{
   void Foo() {}
};

// std::is_standard_layout<Derived> == true
struct Derived : public Base
{
   int x;
   int y;
};

Pochodne byłyby również układem standardowym, gdyby Base miały składowe danych i Derived miały tylko funkcje składowe.

Typy zasobników

Gdy klasa lub struktura jest zarówno trywialny, jak i standardowy, jest to typ ZASOBNIKA (zwykły stary dane). Układ pamięci typów zasobników jest zatem ciągły, a każdy element członkowski ma wyższy adres niż element członkowski, który został zadeklarowany przed nim, dzięki czemu bajt dla kopii bajtów i binarnych operacji we/wy można wykonać na tych typach. Typy skalarne, takie jak int, są również typami zasobników. Typy zasobników, które są klasami, mogą mieć tylko typy zasobników jako niestatyczne składowe danych.

Przykład

W poniższym przykładzie przedstawiono różnice między prostymi, standardowym układem i typami zasobników:

#include <type_traits>
#include <iostream>

using namespace std;

struct B
{
protected:
   virtual void Foo() {}
};

// Neither trivial nor standard-layout
struct A : B
{
   int a;
   int b;
   void Foo() override {} // Virtual function
};

// Trivial but not standard-layout
struct C
{
   int a;
private:
   int b;   // Different access control
};

// Standard-layout but not trivial
struct D
{
   int a;
   int b;
   D() {} //User-defined constructor
};

struct POD
{
   int a;
   int b;
};

int main()
{
   cout << boolalpha;
   cout << "A is trivial is " << is_trivial<A>() << endl; // false
   cout << "A is standard-layout is " << is_standard_layout<A>() << endl;  // false

   cout << "C is trivial is " << is_trivial<C>() << endl; // true
   cout << "C is standard-layout is " << is_standard_layout<C>() << endl;  // false

   cout << "D is trivial is " << is_trivial<D>() << endl;  // false
   cout << "D is standard-layout is " << is_standard_layout<D>() << endl; // true

   cout << "POD is trivial is " << is_trivial<POD>() << endl; // true
   cout << "POD is standard-layout is " << is_standard_layout<POD>() << endl; // true

   return 0;
}

Typy literałów

Typ literału to typ, którego układ można określić w czasie kompilacji. Poniżej przedstawiono typy literałów:

  • void
  • typy skalarne
  • odwołania
  • Tablice typu void, typów skalarnych lub odwołań
  • Klasa, która ma trywialny destruktor i co najmniej jeden konstruktor constexpr, które nie są przenoszone ani konstruktory kopiowane. Ponadto wszystkie niestatyczne składowe danych i klasy bazowe muszą być typami literałów i nietrwałe.

Zobacz też

Podstawowe pojęcia