Sdílet prostřednictvím


Triviální typy, standardní rozložení, POD a literál

Rozložení termínu odkazuje na způsob uspořádání členů objektu třídy, struktury nebo sjednocovacího typu v paměti. V některých případech je rozložení dobře definované specifikací jazyka. Pokud ale třída nebo struktura obsahuje určité funkce jazyka C++, jako jsou virtuální základní třídy, virtuální funkce, členy s jiným řízením přístupu, pak kompilátor je volný zvolit rozložení. Toto rozložení se může lišit v závislosti na tom, jaké optimalizace se provádějí, a v mnoha případech nemusí objekt ani zabírat souvislou oblast paměti. Pokud má například třída virtuální funkce, můžou všechny instance této třídy sdílet jednu tabulku virtuálních funkcí. Tyto typy jsou velmi užitečné, ale mají také omezení. Vzhledem k tomu, že rozložení není definováno, nelze je předat programům napsaným v jiných jazycích, jako je C, a protože nemusí být souvislé, nelze je spolehlivě kopírovat pomocí rychlých funkcí nízké úrovně, jako memcopyje , nebo serializován přes síť.

Aby bylo možné povolit kompilátory i programy a metaprogramy jazyka C++, aby zdůvodněly vhodnost libovolného typu pro operace, které závisí na konkrétním rozložení paměti, jazyk C++14 zavedl tři kategorie jednoduchých tříd a struktur: triviální, standardní rozložení a pod nebo prostý starý data. Standardní knihovna obsahuje šablony is_trivial<T>funkcí a is_pod<T> určují, is_standard_layout<T> zda daný typ patří do dané kategorie.

Triviální typy

Pokud má třída nebo struktura v jazyce C++ zadaný kompilátor nebo explicitně výchozí speciální členské funkce, jedná se o triviální typ. Zabírá souvislou oblast paměti. Může mít členy s různými specifikátory přístupu. V jazyce C++ může kompilátor zvolit, jak v této situaci uspořádat členy. Proto můžete memcopy takové objekty, ale nemůžete je spolehlivě využívat z programu jazyka C. Triviální typ T lze zkopírovat do pole znaku nebo znaku bez znaménka a bezpečně zkopírovat zpět do proměnné T. Mějte na paměti, že kvůli požadavkům na zarovnání můžou být mezi členy typu odsazení bajtů.

Triviální typy mají triviální výchozí konstruktor, triviální konstruktor kopírování, triviální operátor přiřazení kopírování a triviální destruktor. V každém případě triviální znamená, že konstruktor/operator/destructor není zadaný uživatelem a patří do třídy, která má

  • žádné virtuální funkce ani virtuální základní třídy,

  • žádné základní třídy s odpovídajícím jiným než triviálním konstruktorem/operator/destructor

  • žádné datové členy typu třídy s odpovídajícím jiným než triviálním konstruktorem/operator/destructor

Následující příklady ukazují triviální typy. V Trivial2 přítomnost konstruktoru Trivial2(int a, int b) vyžaduje, abyste zadali výchozí konstruktor. Aby se typ kvalifikoval jako triviální, musíte explicitně použít tento konstruktor.

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

Standardní typy rozložení

Pokud třída nebo struktura neobsahuje určité jazykové funkce jazyka C++, jako jsou virtuální funkce, které nejsou nalezeny v jazyce C, a všichni členové mají stejné řízení přístupu, je to standardní typ rozložení. Je to memcopy-schopnost a rozložení je dostatečně definováno, že ho mohou využívat programy jazyka C. Standardní typy rozložení můžou mít uživatelsky definované speciální členské funkce. Standardní typy rozložení mají navíc tyto vlastnosti:

  • žádné virtuální funkce ani virtuální základní třídy

  • Všechny nestatické datové členy mají stejné řízení přístupu.

  • Všechny nestatické členy typu třídy jsou standardní rozložení

  • Všechny základní třídy jsou standardní rozložení

  • nemá žádné základní třídy stejného typu jako první nestatický datový člen.

  • splňuje jednu z těchto podmínek:

    • žádný nestatický datový člen ve většině odvozených tříd a ne více než jedna základní třída s nestatickými datovými členy nebo

    • nemá žádné základní třídy s nestatovými datovými členy.

Následující kód ukazuje jeden příklad standardního typu rozložení:

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

Poslední dva požadavky můžou být možná lépe ilustrovány kódem. V dalším příkladu, i když je Base standardní rozložení, není standardní rozložení, Derived protože je to jak ona (nejvíce odvozená třída), tak Base mají nestatické datové členy:

struct Base
{
   int i;
   int j;
};

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

V tomto příkladu Derived je standardní rozložení, protože Base neobsahuje statické datové členy:

struct Base
{
   void Foo() {}
};

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

Odvozené by také bylo standardní rozložení, pokud Base by měly datové členy a Derived měly pouze členské funkce.

Typy PODů

Pokud je třída nebo struktura triviální i standardní rozložení, jedná se o typ POD (Plain Old Data). Rozložení paměti typů POD je proto souvislé a každý člen má vyšší adresu než člen, který byl deklarován před ním, takže bajt pro kopie bajtů a binární vstupně-výstupní operace lze u těchto typů provádět. Skalární typy, například int, jsou také typy POD. Typy POD, které jsou třídami, mohou mít pouze typy POD jako nestatické datové členy.

Příklad

Následující příklad ukazuje rozdíly mezi triviálními typy, standardním rozložením a typy POD:

#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 literálů

Typ literálu je typ, jehož rozložení lze určit v době kompilace. Toto jsou typy literálů:

  • void
  • skalární typy
  • odkazy
  • Pole void, skalárních typů nebo odkazů
  • Třída, která má triviální destruktor a jeden nebo více konstruktorů constexpr, které nejsou přesunout nebo kopírovat konstruktory. Kromě toho všechny její nestatické datové členy a základní třídy musí být literální typy a nevolatelní.

Viz také

Základní koncepty