Тривиальные типы, типы стандартной структуры, POD и типы литералов

Термин структура относится к организации членов объекта класса, структуры или типа объединения в памяти. В некоторых случаях структура четко определена спецификациями языка. Однако если класс или структура содержит определенные возможности языка C++, такие как виртуальные базовые классы, виртуальные функции, члены с разным уровнем управления доступом, компилятор может выбрать структуру самостоятельно. Эта структура может сильно отличаться в зависимости от того, какие оптимизации выполняются, и во многих случаях объект может даже не занимать непрерывную область памяти. Например, если класс имеет виртуальные функции, все экземпляры этого класса могут иметь одну общую таблицу виртуальных функций. Такие типы очень удобны, однако им присущи определенные ограничения. Поскольку структура не определена, их нельзя передавать программам, написанным на других языках, таких как C, а из-за того что они могут быть ненепрерывными, для них не поддерживается надежное копирование с помощью быстрых низкоуровневых функций, таких как memcopy, или сериализация по сети.

Чтобы дать возможность компиляторам, а также программам и метапрограммам C++ определять пригодность того или иного типа для операций, зависящих от конкретной структуры памяти, в C++14 представлены три категории простых классов и структур: тривиальные, стандартной структуры и POD (простые старые данные). Стандартная библиотека содержит шаблоны функций is_trivial<T>, is_standard_layout<T> и is_pod<T>, которые определяют, принадлежит ли данный тип данной категории.

Тривиальные типы

Если класс или структура в C++ включает предоставляемые компилятором или явно задаваемые по умолчанию специальные функции-члены — это тривиальный тип. Он занимает непрерывную область памяти. Он может иметь члены с разными спецификаторами доступа. В C++ компилятор может самостоятельно выбирать способ упорядочивания членов в этой ситуации. Таким образом вы можете копировать такие объекты с помощью memcopy, однако надежное использование их из программы на языке C невозможно. Тривиальный тип T можно скопировать в массив значений char или unsigned char и безопасно скопировать обратно в переменную T. Обратите внимание, что из-за требований к выравниванию, между членами типа могут существовать байты заполнения.

Тривиальные типы имеют тривиальный конструктор по умолчанию, тривиальный конструктор копирования, тривиальный оператор назначения копирования и тривиальный деструктор. В любом случае тривиальный означает, что конструктор, оператор или деструктор не предоставляется пользователем и принадлежит к классу, у которого

  • нет виртуальных функций или виртуальных базовых классов;

  • нет базовых классов с соответствующим нетривиальным конструктором, оператором или деструктором;

  • нет членов данных типа класса с соответствующим нетривиальным конструктором, оператором или деструктором.

Ниже приведены примеры тривиальных типов. В Trivial2 наличие конструктора Trivial2(int a, int b) требует указания конструктора по умолчанию. Чтобы тип мог считаться тривиальным, необходимо явно задать конструктор по умолчанию.

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

Типы стандартной структуры

Если класс или структура не содержит определенные возможности языка C++, такие как виртуальные функции, которых нет в языке C, и все элементы имеют один и тот же уровень управления доступом — это тип стандартной структуры. Его можно скопировать с помощью функции memcopy, а структура достаточно определена, чтобы его могли использовать программы на языке C. Типы стандартной структуры могут иметь определенные пользователем специальные функции-члены. Кроме того, типы стандартной структуры имеют следующие характеристики:

  • нет виртуальных функций или виртуальных базовых классов;

  • все нестатические члены данных имеют один уровень управления доступом;

  • все нестатические члены типа класса относятся к стандартному макету;

  • все базовые классы относятся к стандартной структуре;

  • нет базовых классов того же типа, что и первый нестатический член данных;

  • соответствуют одному из следующих условий:

    • нет нестатических членов данных в наиболее производном классе и не более одного базового класса с нестатическими членами данных или

    • нет базовых классов с нестатическими членами данных.

Ниже показан пример кода типа стандартной структуры:

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

Последние два требования, возможно, проще проиллюстрировать на примере кода. В следующем примере, хотя Base относится к типу стандартной структуры, Derived не является стандартным макетом, так как он (наиболее производный класс) и Base имеют нестатические члены данных:

struct Base
{
   int i;
   int j;
};

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

В этом примере Derived — тип стандартной структуры, поскольку у Base нет нестатических членов данных:

struct Base
{
   void Foo() {}
};

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

Производный класс также будет относиться к стандартной структуре, если у Base есть члены данных, а у Derived — только функции-члены.

Типы POD

Если класс или структура является тривиальным типом и типом стандартной структуры — это тип POD (обычные старые данные). Таким образом, распределение памяти для типов POD является непрерывным, и адрес каждого члена выше, чем адрес члена, объявленного до него, что дает возможность выполнять побайтовое копирование и двоичный ввод-вывод для этих типов. Скалярные типы, такие как int, также являются типами POD. Типы POD, которые являются классами, могут содержать только типы POD в качестве нестатических членов данных.

Пример

В следующем примере показаны различия между тривиальными типами, типами стандартной структуры и 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;
}

Литеральные типы

Тип литерала — это такой тип, макет которого может быть определен во время компиляции. Ниже указаны типы литералов.

  • void
  • скалярные типы
  • ссылки
  • Массивы void, скалярных типов или ссылок
  • Класс, имеющий тривиальный деструктор, а также один или несколько конструкторов constexpr, которые не являются конструкторами перемещений или копий. Кроме того, все его нестатические данные-члены и базовые классы должны быть типами литералов и не должны изменяться.

См. также

Основные понятия