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ż
Opinia
https://aka.ms/ContentUserFeedback.
Dostępne już wkrótce: W 2024 r. będziemy stopniowo wycofywać zgłoszenia z serwisu GitHub jako mechanizm przesyłania opinii na temat zawartości i zastępować go nowym systemem opinii. Aby uzyskać więcej informacji, sprawdź:Prześlij i wyświetl opinię dla