Что такое агрегаты и POD и как / почему они особенные?

473

Этот FAQ относится к агрегатам и POD и охватывает следующий материал:

  • Что такое Агрегаты?
  • Что такое POD (Обычные старые данные)?
  • Как они связаны?
  • Как и почему они особенные?
  • Какие изменения для С++ 11?
Теги:
c++11
c++17
aggregate
pod
c++-faq

6 ответов

473

Как читать:

Эта статья довольно длинная. Если вы хотите узнать об обоих агрегатах и ​​POD (простые старые данные), найдите время и прочитайте его. Если вас интересуют только агрегаты, прочитайте только первую часть. Если вас интересуют только POD, тогда вы должны сначала прочитать определение, последствия и примеры агрегатов, а затем вы можете перейти к POD, но я бы по-прежнему рекомендовал прочитать первую часть целиком. Понятие агрегатов важно для определения POD. Если вы обнаружите какие-либо ошибки (даже незначительные, включая грамматику, стилистику, форматирование, синтаксис и т.д.), Оставьте комментарий, я отредактирую.

Что такое агрегаты и почему они являются специальными

Формальное определение из стандарта С++ (С++ 03 8.5.1 §1):

Агрегат - это массив или класс (раздел 9) без объявления пользователем конструкторы (12.1), нет частных или защищенных нестатических данных (раздел 11), нет базовых классов (раздел 10) и нет виртуальных функций (10.3).

Итак, ОК, давайте проанализировать это определение. Прежде всего, любой массив является агрегатом. Класс также может быть агрегатом, если... подождите! ничего не говорится о структурах или союзах, не могут ли они быть агрегатами? Да, они могут. В С++ термин class относится ко всем классам, структурам и объединениям. Таким образом, класс (или структура или объединение) является агрегатом тогда и только тогда, когда он удовлетворяет критериям из приведенных выше определений. Что подразумевают эти критерии?

  • Это не означает, что у агрегатного класса не могут быть конструкторы, на самом деле он может иметь конструктор по умолчанию и/или конструктор копирования, если они неявно объявлены компилятором, а не явно пользователем

  • Нет частных или защищенных нестатических элементов данных. У вас может быть так много частных и защищенных функций-членов (но не конструкторов), а также как много частных или защищенных статических элементов данных и функций-членов по своему усмотрению, а не нарушать правила для агрегатных классов

  • Агрегатный класс может иметь пользовательский объявленный/определяемый пользователем оператор копирования и/или деструктор

  • Массив - это агрегат, даже если он представляет собой массив неагрессивного типа класса.

Теперь рассмотрим несколько примеров:

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

Вы получаете идею. Теперь посмотрим, как агрегаты являются особыми. Они, в отличие от неагрегатных классов, могут быть инициализированы фигурными фигурными скобками {}. Этот синтаксис инициализации широко известен для массивов, и мы просто узнали, что они являются агрегатами. Итак, начнем с них.

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

if (m == n)
 элемент я th массива инициализируется с помощью i
else if (m < n)
первые m элементов массива инициализируются с помощью 1, 2,..., a m, а остальные элементы n - m если возможно, инициализировать значение (см. ниже пояснение термина)
else if (m > n)
 компилятор выдает ошибку
else (это тот случай, когда n вообще не указывается как int a[] = {1, 2, 3};)
 размер массива (n) считается равным m, поэтому int a[] = {1, 2, 3}; эквивалентно int a[3] = {1, 2, 3};

Когда объект скалярного типа (bool, int, char, double, указатели и т.д.) инициализируется значением, он означает, что для этого типа он инициализируется 0 (false для bool, 0.0 для double и т.д.). Когда объект типа класса с объявленным пользователем конструктором по умолчанию инициализируется значением, его вызывающий конструктор по умолчанию. Если конструктор по умолчанию неявно определен, то все нестатические члены рекурсивно инициализируются значением. Это определение неточно и немного неверно, но оно должно дать вам основную идею. Ссылка не может быть инициализирована значением. Инициализация значения для неагрегатного класса может завершиться неудачей, если, например, у класса нет соответствующего конструктора по умолчанию.

Примеры инициализации массива:

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
}

Теперь посмотрим, как агрегатные классы могут быть инициализированы скобками. Точно так же. Вместо элементов массива мы инициализируем нестатические члены данных в порядке их появления в определении класса (все они общедоступны по определению). Если число инициаторов меньше, чем членов, остальные инициализируются значением. Если невозможно инициализировать значение одного из членов, которые не были явно инициализированы, мы получаем ошибку времени компиляции. Если есть больше инициализаторов, чем необходимо, мы также получаем ошибку времени компиляции.

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

В приведенном выше примере y.c инициализируется 'a', y.x.i1 с 10, y.x.i2 с 20, y.i[0] с 20, y.i[1] с 30 и y.f инициализируется значением, то есть инициализируется с помощью 0.0. Защищенный статический член d не инициализируется вообще, потому что это static.

Совокупные союзы отличаются друг от друга тем, что вы можете инициализировать только их первый член с фигурными скобками. Я думаю, что если вы достаточно продвинуты на С++, чтобы даже рассмотреть возможность использования профсоюзов (их использование может быть очень опасным и нужно тщательно обдумать), вы могли бы сами найти правила для профсоюзов в стандарте:).

Теперь, когда мы знаем, что особенно важно для агрегатов, попробуйте понять ограничения на классы; то есть, почему они там. Мы должны понимать, что поэтапная инициализация с помощью фигурных скобок подразумевает, что класс есть не что иное, как сумма его членов. Если пользовательский конструктор присутствует, это означает, что пользователю необходимо выполнить дополнительную работу для инициализации элементов, поэтому инициализация скобки будет неправильной. Если присутствуют виртуальные функции, это означает, что объекты этого класса имеют (в большинстве реализаций) указатель на так называемую vtable класса, который установлен в конструкторе, поэтому инициализация скобок будет недостаточной. Вы могли бы выяснить остальную часть ограничений аналогично упражнению:).

Достаточно об агрегатах. Теперь мы можем определить более строгий набор типов, например, PODs

Что такое POD и почему они являются специальными

Формальное определение из стандарта С++ (С++ 03 9 § 4):

POD-struct - это совокупный класс который не имеет нестатических данных тип non-POD-struct, не-POD-union (или массив таких типов) или ссылку, и не имеет назначенного пользователем назначения копирования оператора и не определяется пользователем деструктор. Аналогично, POD-union объединенный союз, который не имеет нестатические элементы данных типа не-POD-структура, не-POD-объединение (или массив таких типов) или ссылку, и не имеет назначенного пользователем назначения копирования оператора и не определяется пользователем деструктор. Класс POD - это класс это либо POD-структура, либо POD-союз.

Ничего себе, это сложнее разобрать, не так ли?:) Оставьте профсоюзы (на тех же основаниях, что и выше), и перефразируйте немного более четко:

Агрегатный класс называется POD, если он не имеет заданного пользователем назначения копирования оператора и деструктора, и ни один из его нестатические члены являются не-POD класс, массив не-POD или ссылка.

Что означает это определение? (Я упоминал POD означает Обычные старые данные?)

  • Все классы POD являются агрегатами или, наоборот, если класс не является совокупностью, то он не является POD
  • Классы, как и структуры, могут быть POD, хотя стандартный термин POD-struct для обоих случаев
  • Как и в случае с агрегатами, неважно, какие статические члены класса имеют

Примеры:

struct POD
{
  int x;
  char y;
  void f() {} //no harm if there 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
};

POD-классы, POD-объединения, скалярные типы и массивы таких типов коллективно называются POD-типами.
POD являются особыми во многих отношениях. Я приведу лишь несколько примеров.

  • POD-классы наиболее близки к C-структурам. В отличие от них, POD могут иметь функции-члены и произвольные статические члены, но ни одна из этих двух не меняет макет памяти объекта. Поэтому, если вы хотите написать более или менее портативную динамическую библиотеку, которая может использоваться с C и даже .NET, вы должны попытаться сделать все экспортированные функции и вернуть только параметры типов POD.

  • Время жизни объектов класса не-POD начинается с того момента, когда конструктор завершил работу и закончил работу с завершением деструктора. Для классов POD время жизни начинается, когда хранилище для объекта занято и заканчивается, когда это хранилище выпущено или повторно использовано.

  • Для объектов типов POD гарантируется стандартом, что при memcpy содержимое вашего объекта в массив char или unsigned char, а затем memcpy содержимое обратно в ваш объект, объект будет иметь свое первоначальное значение. Обратите внимание, что нет такой гарантии для объектов не-POD-типов. Кроме того, вы можете безопасно копировать объекты POD с помощью memcpy. В следующем примере предполагается, что T является 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
    
  • инструкция goto. Как вы, возможно, знаете, это незаконно (компилятор должен выпустить ошибку), чтобы совершить переход через goto из точки, где какая-то переменная еще не была в области до точки, где она уже находится в области видимости. Это ограничение применяется только в том случае, если переменная имеет тип не-POD. В следующем примере f() плохо сформировался, тогда как g() хорошо сформирован. Обратите внимание, что компилятор Microsoft слишком либеральен с этим правилом - он просто выдает предупреждение в обоих случаях.

    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;
    }
    
  • Гарантируется, что в начале объекта POD не будет заполнения. Другими словами, если первый член POD-класса A имеет тип T, вы можете безопасно reinterpret_cast от A* до T* и получить указатель на первый член и наоборот.

Список можно продолжать и продолжать...

Заключение

Важно понять, что такое POD, потому что многие языковые функции, как вы видите, ведут себя по-другому для них.

  • 3
    Хороший ответ. Комментарии: «Если конструктор по умолчанию определен неявно, то все нестатические члены инициализируются рекурсивно-значениями». и «Инициализация значения для неагрегированного класса может завершиться неудачей, если, например, у класса нет соответствующего конструктора по умолчанию». не правильно: Инициализация значения класса с неявно объявленным конструктором по умолчанию не требует неявно определенного конструктора по умолчанию. Таким образом, учитывая (вставьте private: при необходимости): struct A { int const a; }; тогда A() будет корректным, даже если определение конструктора A умолчанию будет некорректным.
  • 0
    Это не совсем FAQ, это слишком долго. Также, пожалуйста, укажите ваши аббревиатуры, указанные в первом абзаце документа, если это возможно, вместо 2/3.
Показать ещё 21 комментарий
383

Какие изменения для С++ 11?

Заполнители

Стандартное определение агрегата немного изменилось, но оно все же почти одинаково:

Агрегат - это массив или класс (раздел 9) без конструкторов, предоставляемых пользователем (12.1), нет элементов привязки или равных инициализаторов для нестатических элементов данных (9.2), нет личных или защищенных нестатические члены данных (раздел 11), базовые классы (раздел 10) и виртуальные функции (10.3).

Хорошо, что изменилось?

  • Ранее агрегат не мог иметь никаких объявленных пользователем конструкторов, но теперь он не может иметь созданных пользователем конструкторов. Есть ли разница? Да, есть, потому что теперь вы можете объявить конструкторы и по умолчанию:

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

    Это все еще агрегат, потому что конструктор (или любая специальная функция-член), который по умолчанию используется в первом объявлении, не предоставляется пользователям.

  • Теперь у агрегата не может быть каких-либо выравнивающих или равных инициализаторов для нестатических членов данных. Что это значит? Ну, это только потому, что с помощью этого нового стандарта мы можем инициализировать членов непосредственно в классе следующим образом:

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

    Использование этой функции делает класс более не агрегированным, потому что он в основном эквивалентен предоставлению собственного конструктора по умолчанию.

Итак, что такое совокупность, совсем не изменилось. Это по-прежнему та же основная идея, адаптированная к новым функциям.

Как насчет POD?

POD прошли множество изменений. Многие новые правила о POD были ослаблены в этом новом стандарте, и способ определения в стандарте был радикально изменен.

Идея POD состоит в том, чтобы зафиксировать в основном два разных свойства:

  • Он поддерживает статическую инициализацию и
  • Компиляция POD в С++ дает вам тот же макет памяти, что и структура, скомпилированная в C.

Из-за этого определение было разделено на два разных понятия: тривиальные классы и классы стандартного макета, потому что они более полезны, чем POD. Стандарт теперь редко использует термин POD, предпочитая более конкретные тривиальные и стандартные макеты.

Новое определение в основном говорит о том, что POD - это класс, который является тривиальным и имеет стандартный макет, и это свойство должно содержать рекурсивно для всех нестатических членов данных:

Структура POD - это неединичный класс, который является как тривиальным классом, так и стандартным классом макета, и не имеет нестатических членов данных типа non-POD struct, non-POD union (или массив таких типов). Аналогичным образом, объединение POD представляет собой объединение, которое является тривиальным классом и стандартным классом макета, и имеет не нестатические члены данных типа non-POD struct, не-POD union (или массив таких типов). Класс POD - это класс, который представляет собой либо структуру POD, либо объединение POD.

Перейдем к каждому из этих двух свойств отдельно.

Тривиальные классы

Тривиальным является первое свойство, упомянутое выше: тривиальные классы поддерживают статическую инициализацию. Если класс тривиально-копируемый (надмножество тривиальных классов), можно скопировать его представление по месту такими вещами, как memcpy, и ожидать, что результат будет таким же.

Стандарт определяет тривиальный класс следующим образом:

Тривиально-скопируемый класс - это класс, который:

- нет нетривиальных конструкторов копирования (12.8),

- нет нетривиальных конструкторов перемещения (12.8),

- нет нетривиальных операторов присваивания копий (13.5.3, 12.8),

- нет нетривиальных операторов присваивания перемещения (13.5.3, 12.8) и

- имеет тривиальный деструктор (12.4).

Тривиальный класс - это класс, имеющий тривиальный конструктор по умолчанию (12.1) и тривиально копируемый.

[Примечание. В частности, тривиально-копируемый или тривиальный класс не имеет виртуальных функций или виртуальных базовых классов.-end note]

Итак, каковы все эти тривиальные и нетривиальные вещи?

Конструктор копирования/перемещения для класса X тривиален, если он не предоставляется пользователем и если

- класс X не имеет виртуальных функций (10.3) и виртуальных базовых классов (10.1) и

- конструктор, выбранный для копирования/перемещения каждого подобъекта прямого базового класса, тривиален и

- для каждого нестатического элемента данных X, относящегося к типу класса (или его массиву), конструктор выбранный для копирования/перемещения этого элемента тривиально,

в противном случае конструктор copy/move не является тривиальным.

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

Определение тривиального оператора присваивания копирования/перемещения очень похоже, просто заменив слово "конструктор" на "оператор присваивания".

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

И еще одно подобное правило существует для тривиальных конструкторов по умолчанию, с добавлением, что конструктор по умолчанию не является тривиальным, если класс имеет нестатические элементы данных с элементами скрепления или равенства, которые мы видели выше.

Вот несколько примеров, чтобы очистить все:

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

Стандарт-макет

Стандартная макет - это второе свойство. В стандарте упоминается, что они полезны для общения с другими языками и потому, что класс стандартного макета имеет тот же макет памяти эквивалентной структуры или объединения C.

Это другое свойство, которое должно содержать рекурсивно для членов и всех базовых классов. И, как обычно, виртуальные функции или виртуальные базовые классы не допускаются. Это сделало бы макет несовместимым с C.

В этом случае расслабленное правило состоит в том, что классы стандартного макета должны иметь все нестатические элементы данных с одним и тем же контролем доступа. Раньше они должны были быть общедоступными, но теперь вы можете сделать их частными или защищенными, если они все закрыты или защищены.

При использовании наследования только один класс во всем дереве наследования может иметь нестатические элементы данных, а первый нестатический член данных не может быть типа базового класса (это может нарушить правила псевдонимов), в противном случае оно не будет класс стандартного макета.

Это определение содержится в стандартном тексте:

Класс стандартного макета - это класс, который:

- не содержит нестатических элементов данных класса нестандартного макета (или массива таких типов) или ссылку,

- не имеет виртуальных функций (10.3) и виртуальных базовых классов (10.1),

- имеет тот же контроль доступа (раздел 11) для всех нестатических членов данных,

- не имеет базовых классов нестандартной компоновки,

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

- не имеет базовых классов того же типа, что и первый нестатический элемент данных.

Структура стандартного макета - это класс стандартного макета, определенный с помощью структуры класса или структуры класс класса.

Стандартное соединение макета - это класс стандартного макета, определенный с помощью объединения классов классов.

[Примечание. Стандартные классы макета полезны для общения с кодом, написанным на других языках программирования. Их макет указан в примечании 9.2.-end]

И рассмотрим несколько примеров.

// empty classes have standard-layout
struct StandardLayout1 {};

struct StandardLayout2 {
    int x;
};

struct StandardLayout3 {
private: // both are private, so it 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

Заключение

С этими новыми правилами намного больше типов могут быть POD сейчас. И даже если тип не POD, мы можем использовать некоторые из свойств POD отдельно (если это только один из тривиальных или стандартных макетов).

Стандартная библиотека имеет свойства для проверки этих свойств в заголовке <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;
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;
  • 1
    не могли бы вы разработать следующие правила: a) классы стандартной компоновки должны иметь все нестатические элементы данных с одинаковым контролем доступа; b) только один класс во всем дереве наследования может иметь нестатические элементы данных, а первый нестатический элемент данных не может иметь тип базового класса (это может нарушить правила псевдонимов). Особенно каковы причины для них? Для более позднего правила, можете ли вы привести пример прерывания псевдонимов?
  • 0
    @AndyT: Смотри мой ответ. Я пытался ответить, насколько мне известно.
Показать ещё 6 комментариев
87

Что изменилось для С++ 14

Мы можем обратиться к проекту стандарта С++ 14 для справки.

сводные показатели

Это описано в разделе 8.5.1 Агрегаты, в котором дано следующее определение:

Агрегат - это массив или класс (раздел 9) без предоставленных пользователем конструкторов (12.1), без закрытых или защищенных нестатических элементов данных (раздел 11), без базовых классов (раздел 10) и без виртуальных функций (10.3).

Единственное изменение - добавление инициализаторов членов класса не делает класс неагрегированным. Итак, следующий пример из С++ 11 агрегатной инициализации для классов с инициализированными членами-инициализаторами:

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

не был агрегатом в С++ 11, но в С++ 14. Это изменение описано в N3605: Инициализаторы и агрегаты элементов, которые имеют следующее резюме:

Бьярн Страуструп и Ричард Смит подняли вопрос о том, что инициализация агрегатов и инициализаторы членов не работают вместе. В этом документе предлагается решить эту проблему, приняв предложенную Смитом формулировку, которая снимает ограничение, согласно которому агрегаты не могут иметь инициализаторы элементов.

POD остается прежним

Определение структуры POD (обычные старые данные) описано в разделе 9 Классы, в котором говорится:

Структура 110 POD - это класс, не являющийся объединением, который является как тривиальным классом, так и классом стандартной компоновки, и не имеет нестатических членов-данных типа не-POD-структуры, не-POD-объединения (или массива таких типов). Аналогично, объединение POD - это объединение, которое является одновременно тривиальным классом и классом стандартной компоновки и не имеет нестатических членов-данных типа non-POD struct, non-POD union (или массива таких типов). Класс POD - это класс, который является либо структурой POD, либо объединением POD.

это та же формулировка, что и в С++ 11.

Изменения стандартного макета для С++ 14

Как отмечалось в комментариях, модуль опирается на определение стандартного макета, и это изменилось для С++ 14, но это произошло с помощью отчетов о дефектах, которые были применены к С++ 14 после факта.

Было три ДР:

Итак, стандартная раскладка пошла из этого Pre С++ 14:

Класс стандартного макета - это класс, который:

  • (7.1) не имеет нестатических членов-данных типа нестандартного класса макета (или массива таких типов) или ссылки,
  • (7.2) не имеет виртуальных функций ([class.virtual]) и виртуальных базовых классов ([class.mi]),
  • (7.3) имеет одинаковый контроль доступа (пункт [class.access]) для всех нестатических элементов данных,
  • (7.4) не имеет базовых классов нестандартной компоновки,
  • (7.5) либо не имеет нестатических членов данных в самом производном классе и не более одного базового класса с нестатическими членами данных, или не имеет базовых классов с нестатическими членами данных, и
  • (7.6) не имеет базовых классов того же типа, что и первый нестатический член данных.109

На это в С++ 14:

Класс S является классом стандартной компоновки, если он:

  • (3.1) не имеет нестатических членов-данных типа нестандартного класса макета (или массива таких типов) или ссылки,
  • (3.2) не имеет виртуальных функций и виртуальных базовых классов,
  • (3.3) имеет одинаковый контроль доступа для всех нестатических элементов данных,
  • (3.4) не имеет нестандартных макетов базовых классов,
  • (3.5) имеет не более одного подобъекта базового класса любого данного типа,
  • (3.6) имеет все нестатические члены-данные и битовые поля в классе, а его базовые классы сначала объявлены в одном и том же классе, и
  • (3.7) не имеет элемента набора M (S) типов в качестве базового класса, где для любого типа X M (X) определяется следующим образом.104 [Примечание: M (X) - это набор типов все подобъекты не базового класса, которые могут иметь нулевое смещение в X. - конец примечания]
    • (3.7.1) Если X является типом класса без объединения, в котором нет (возможно, унаследованных) нестатических членов-данных, множество M (X) пусто.
    • (3.7.2) Если X - это тип класса, не являющийся объединением, с нестатическим элементом данных типа X0, который имеет нулевой размер или является первым нестатическим элементом данных в X (где указанный член может быть анонимным объединением), множество M (X) состоит из X0 и элементов из M (X0).
    • (3.7.3) Если X является типом объединения, множество M (X) является объединением всех M (Ui) и набора, содержащего все Ui, где каждый Ui является типом i-го элемента нестатических данных X,
    • (3.7.4) Если X является типом массива с типом элемента Xe, множество M (X) состоит из Xe и элементов M (Xe).
    • (3.7.5) Если X не класс, не массив, то множество M (X) пусто.
  • 4
    Существует предложение разрешить агрегатам иметь базовый класс, если он является конструируемым по умолчанию, см. N4404.
  • 0
    в то время как POD может остаться прежним, C ++ 14 StandardLayoutType, который является требованием для POD, изменился в соответствии с cppref: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Показать ещё 3 комментария
39

Вы можете продумать следующие правила:

Я попробую:

a) классы стандартного макета должны иметь все нестатические элементы данных с одинаковым контролем доступа

Это просто: все нестатические члены данных должны быть public, private или protected. Вы не можете иметь некоторые public и некоторые private.

Причиной для них является рассуждение о том, чтобы различать "стандартную компоновку" и "не стандартную компоновку" вообще. А именно, дать компилятору свободу выбора, как поместить вещи в память. Это не только ссылки на vtable.

Назад, когда они стандартизировали С++ в 98, они должны были в основном предсказать, как люди будут его реализовывать. Хотя у них было довольно много опыта внедрения с различными вкусами С++, они не были уверены в вещах. Поэтому они решили быть осторожными: дать компиляторам как можно больше свободы.

Вот почему определение POD на С++ 98 настолько строгое. Это дало компиляторам С++ большую широту в макете участника для большинства классов. В принципе, типы POD были предназначены для особых случаев, что вы специально писали по какой-то причине.

Когда С++ 11 работал, у них было много опыта работы с компиляторами. И они поняли, что... С++ компиляторы очень ленивы. У них была вся эта свобода, но они ничего не делали с ней.

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

Теперь, когда дело дошло до public/private, все по-другому. Свобода переупорядочивать, какие члены public vs. private на самом деле может иметь значение для компилятора, особенно в отладочных сборках. И поскольку точка стандартного макета заключается в совместимости с другими языками, вы не можете иметь разную компоновку в debug vs. release.

Тогда возникает тот факт, что это не повредит пользователю. Если вы делаете инкапсулированный класс, вероятность того, что все ваши члены данных будут private в любом случае. Как правило, вы не публикуете публичные данные на полностью инкапсулированных типах. Таким образом, это будет проблемой только для тех немногих пользователей, которые хотят это сделать, кто хочет это разделение.

Так что это не большая потеря.

b) только один класс во всем дереве наследования может иметь нестатические члены данных,

Причина этого возвращается к тому, почему они снова стандартизировали стандартный макет: обычная практика.

Нет обычной практики, когда речь идет о наличии двух элементов дерева наследования, которые фактически хранят вещи. Некоторые кладут базовый класс перед производным, другие делают это по-другому. В каком порядке вы заказываете участников, если они происходят из двух базовых классов? И так далее. Компиляторы сильно расходятся по этим вопросам.

Кроме того, благодаря правилу zero/one/infinity, как только вы скажете, что у вас есть два класса с членами, вы можете сказать столько, сколько хотите. Это требует добавления большого количества правил компоновки для того, как справиться с этим. Вы должны сказать, как многократное наследование работает, какие классы помещают свои данные перед другими классами и т.д. Это много правил, при очень небольшом материальном выигрыше.

Вы не можете сделать все, что не имеет виртуальных функций, и стандартный макет конструктора по умолчанию.

и первый нестатический элемент данных не может быть типа базового класса (это может нарушить правила псевдонимов).

Я не могу разговаривать с этим. Я недостаточно образован в правилах псевдонимов С++, чтобы действительно понять это. Но это как-то связано с тем, что базовый член будет иметь тот же адрес, что и базовый класс. То есть:

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

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

И это, вероятно, против правил псевдонимов С++. В некотором роде.

Однако учтите это: насколько полезной могла быть способность делать это когда-либо на самом деле? Поскольку только один класс может иметь нестатические элементы данных, тогда Derived должен быть этим классом (поскольку он имеет Base как член). Поэтому Base должен быть пустым (данных). И если Base пуст, а также базовый класс... зачем вообще его член данных?

Так как Base пуст, он не имеет состояния. Таким образом, любые нестатические функции-члены будут делать то, что они делают, основываясь на своих параметрах, а не на указателе this.

Так снова: никаких больших потерь.

  • 0
    Спасибо за объяснение, это очень помогает. Вероятно, несмотря на то, что static_cast<Base*>(&d) и &d.b - это один и тот же тип Base* , они указывают на разные вещи, тем самым нарушая правило псевдонимов. Пожалуйста, поправьте меня.
  • 1
    и почему, если только один класс может иметь нестатические члены-данные, тогда Derived должен быть этим классом?
Показать ещё 2 комментария
13

Изменения в С++ 17

Загрузите окончательный вариант международного стандарта С++ 17 здесь.

сводные показатели

С++ 17 расширяет и улучшает агрегаты и инициализацию агрегатов. Стандартная библиотека также теперь включает класс черты типа std::is_aggregate. Вот формальное определение из разделов 11.6.1.1 и 11.6.1.2 (исключены внутренние ссылки):

Агрегат - это массив или класс с
- нет пользовательских, явных или унаследованных конструкторов,
- нет личных или защищенных нестатических членов данных,
- нет виртуальных функций, и
- нет виртуальных, частных или защищенных базовых классов.
[Примечание. Совокупная инициализация не позволяет получить доступ к защищенным и закрытым членам или конструкторам базового класса. —Конечная записка]
Элементы совокупности:
- для массива элементы массива в порядке возрастания индекса или
- для класса - прямые базовые классы в порядке объявления, за которыми следуют прямые нестатические члены данных, которые не являются членами анонимного объединения, в порядке объявления.

Что изменилось?

  1. Агрегаты теперь могут иметь публичные, не виртуальные базовые классы. Кроме того, не требуется, чтобы базовые классы были агрегатами. Если они не являются агрегатами, они инициализируются списком.
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
    &lt&lt "is C aggregate?: " &lt&lt (std::is_aggregate::value ? 'Y' : 'N')
    &lt&lt " i1: " &lt&lt c.i1 &lt&lt " i2: " &lt&lt c.i2
    &lt&lt " j: " &lt&lt c.j &lt&lt " m.m: " &lt&lt c.m.m &lt&lt endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. Явные конструкторы по умолчанию запрещены
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Наследование конструкторов запрещено
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Тривиальные Классы

Определение тривиального класса было переработано в С++ 17, чтобы устранить несколько дефектов, которые не были устранены в С++ 14. Изменения носили технический характер. Вот новое определение в 12.0.6 (внутренние ссылки исключены):

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

Изменения:

  1. В С++ 14, для того чтобы класс был тривиальным, у класса не могло быть никаких конструкторов/операторов назначения копирования/перемещения, которые были бы нетривиальными. Однако тогда неявно объявленный как конструктор/оператор по умолчанию может быть нетривиальным и, тем не менее, определенным как удаленный, потому что, например, класс содержит подобъект типа класса, который не может быть скопирован/перемещен. Наличие такого нетривиального конструктора/оператора, определяемого как удаленное, приведет к тому, что весь класс будет нетривиальным. Аналогичная проблема существовала с деструкторами. В С++ 17 поясняется, что наличие такого конструктора/операторов не приводит к тому, что класс является нетривиально копируемым, а следовательно, нетривиальным, и что тривиально копируемый класс должен иметь тривиальный не удаляемый деструктор. DR1734, DR1928
  2. С++ 14 разрешил тривиально копируемому классу, а значит, тривиальному классу, иметь каждый конструктор/оператор копирования/перемещения, объявленный как удаленный. Если, например, class также является стандартным макетом, его можно легально скопировать/переместить с помощью std::memcpy. Это было семантическим противоречием, потому что, определяя как удаленные все операторы конструктора/присваивания, создатель класса явно предполагал, что класс не может быть скопирован/перемещен, однако класс все еще соответствует определению тривиально копируемого класса. Следовательно, в С++ 17 у нас есть новое предложение, утверждающее, что тривиально копируемый класс должен иметь хотя бы один тривиальный, не удаленный (хотя и не обязательно общедоступный) оператор конструктора/присваивания копирования/перемещения. См. N4148, DR1734
  3. Третье техническое изменение касается аналогичной проблемы с конструкторами по умолчанию. В С++ 14 класс может иметь тривиальные конструкторы по умолчанию, которые неявно определены как удаленные, но все же остаются тривиальным классом. Новое определение поясняет, что у тривиального класса должен быть хотя бы один тривиальный не удаленный конструктор по умолчанию. См DR1496

Стандартные классы

Определение стандартного макета также было переработано с учетом отчетов о дефектах. Опять же изменения носили технический характер. Вот текст из стандарта (12.0.7). Как и прежде, внутренние ссылки исключены:

Класс S является классом стандартной компоновки, если он:
- не имеет нестатических членов-данных типа нестандартного класса макета (или массива таких типов) или ссылки,
- не имеет виртуальных функций и виртуальных базовых классов,
- имеет одинаковый контроль доступа для всех нестатических элементов данных,
- не имеет нестандартных макетов базовых классов,
- имеет не более одного подобъекта базового класса любого данного типа,
- имеет все нестатические члены-данные и битовые поля в классе и его базовые классы, впервые объявленные в том же классе, и
- не имеет элемента набора M (S) типов (определенных ниже) в качестве базового класса.108
M (X) определяется следующим образом:
- Если X является типом класса, не являющимся объединением, без (возможно, унаследованных) нестатических членов-данных, набор M (X) пуст.
- Если X является типом класса, не являющимся объединением, первый нестатический член данных которого имеет тип X0 (где указанный член может быть анонимным объединением), множество M (X) состоит из X0 и элементов M (X0).
- Если X является типом объединения, множество M (X) является объединением всех M (Ui) и набора, содержащего все Ui, где каждый Ui является типом i-го элемента нестатических данных X.
- Если X является типом массива с типом элемента Xe, множество M (X) состоит из Xe и элементов M (Xe).
- Если X не класс, не массив типа, множество M (X) пусто.
[Примечание: M (X) - это набор типов всех подобъектов не базового класса, для которых в классе стандартной компоновки гарантируется нулевое смещение в X. - конец примечания]
[ Пример:

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
- конец примера]
108) Это гарантирует, что два подобъекта, которые имеют один и тот же тип класса и принадлежат к одному и тому же самому производному объекту, не будут размещены по одному и тому же адресу.

Изменения:

  1. Уточнено, что требование о том, что только один класс в дереве деривации "имеет" нестатические элементы данных, относится к классу, в котором такие элементы данных впервые объявлены, а не к классам, где они могут наследоваться, и расширило это требование до нестатических битовых полей, Также поясняется, что класс стандартной компоновки "имеет не более одного подобъекта базового класса любого данного типа". Смотри DR1813, DR1881
  2. Определение стандартного макета никогда не позволяло типу любого базового класса быть того же типа, что и первый нестатический элемент данных. Это сделано для того, чтобы избежать ситуации, когда элемент данных с нулевым смещением имеет тот же тип, что и любой базовый класс. Стандарт С++ 17 предоставляет более строгое, рекурсивное определение "набора типов всех подобъектов не базового класса, которые гарантированно находятся в классе стандартной компоновки с нулевым смещением", чтобы запретить такие типы от того, чтобы быть типом любого базового класса. Смотри DR1672, DR2120.
  • 0
    Примечание. Я только что обновил свой ответ: стандартные дефекты макета имеют статус CD4, что означает, что они фактически применяются к C ++ 14. Вот почему мой ответ не включал их, потому что это произошло после того, как я написал свой ответ.
  • 0
    Обратите внимание, я начал щедрость по этому вопросу.
Показать ещё 5 комментариев
1

Что изменится для С++ 20

Пока еще рано, поэтому некоторые ответы могут измениться в будущем. Следуя остальной ясной теме этого вопроса, значение и использование агрегатов продолжает меняться с каждым стандартом. На горизонте есть несколько ключевых изменений.

Типы с конструкторами, объявленными пользователем P1008

В С++ 17 этот тип все еще является агрегатом:

struct X {
    X() = delete;
};

И, следовательно, X{} все еще компилируется, потому что это агрегатная инициализация, а не вызов конструктора. Смотрите также: Когда приватный конструктор не приватный конструктор?

В С++ 20 ограничение изменится с требования:

нет пользовательских, explicit или унаследованных конструкторов

в

нет пользовательских или унаследованных конструкторов

Это было принято в рабочий проект С++ 20. Ни X здесь, ни C в связанном вопросе не будут агрегатами в С++ 20.

Инициализация агрегатов из заключенного в скобки списка значений P960

Распространенной проблемой является желание использовать конструкторы emplace() -style с агрегатами:

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

Это не работает, потому что emplace попытается эффективно выполнить инициализацию X(1, 2), что недопустимо. Типичное решение - добавить конструктор в X, но с этим предложением (в настоящее время работающим через Core) агрегаты будут эффективно иметь синтезированные конструкторы, которые делают правильные вещи - и ведут себя как обычные конструкторы. Приведенный выше код будет скомпилирован как есть в С++ 20 (при условии, что эта функция будет одобрена, что кажется вероятным).

Удержание аргумента шаблона класса (CTAD) для агрегатов P1021

В С++ 17 это не компилируется:

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

Point p{1, 2}; // error

Пользователи должны будут написать свое собственное руководство по выводам для всех шаблонов агрегатов:

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

Но так как это в некотором смысле "очевидная вещь", и в основном это просто шаблон, язык сделает это за вас. Это изменение было одобрено Evolution в ноябре 2018 года, поэтому приведенный выше пример, скорее всего, скомпилируется в С++ 20 (без необходимости предоставления руководства по выводам, предоставленного пользователем).

  • 0
    Несмотря на то, что я буду поддерживать голосование, я чувствую, что добавлять это немного рано, но я не знаю ничего значительного, что могло бы изменить это до того, как будет сделан C ++ 2x.
  • 0
    @ShafikYaghmour Да, наверное, слишком рано. Но учитывая, что SD был крайним сроком для новых языковых функций, это единственные два в полете, о которых я знаю - в худшем случае я просто заблокирую удалить один из этих разделов позже? Я только что увидел вопрос, который был активен в отношении щедрости, и подумал, что сейчас самое время принять участие, прежде чем я забуду.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню