Что такое агрегаты и POD и как / почему они особенные? - PullRequest
508 голосов
/ 14 ноября 2010

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

  • Что такое Агрегаты ?
  • Что такое POD с (простые старые данные)?
  • Как они связаны?
  • Как и почему они особенные?
  • Что изменилось в C ++ 11?

Ответы [ 6 ]

523 голосов
/ 14 ноября 2010

Как читать:

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

Этот ответ относится к C ++ 03. Для других стандартов C ++ см .:

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

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

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

Итак, хорошо, давайте разберем это определение. Прежде всего, любой массив является агрегатом. Класс также может быть совокупным, если ... подождите! ничего не сказано о структурах или союзах, разве они не могут быть совокупностями? Да, они могут. В C ++ термин 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] = {a<sub>1</sub>, a<sub>2</sub>, …, a<sub>m</sub>};

if (m == n)
элемент массива i th инициализируется с помощью i
иначе, если (m
первые m элементов массива инициализируются с помощью 1 , a 2 ,…, a m , а другие элементы n - m, если возможно, значение инициализировано (объяснение термина см. Ниже)
иначе, если (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.

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

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

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

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

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

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

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

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

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

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

Примеры:

struct POD
{
  int x;
  char y;
  void f() {} //no harm if there's 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, потому что многие языковые функции, как вы видите, ведут себя по-разному для них.

428 голосов
/ 25 августа 2011

Что изменилось в C ++ 11?

Агрегаты

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

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

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

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

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

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

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

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

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

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

А как насчет POD?

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

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

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

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

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

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

Давайте подробно рассмотрим каждое из этих двух свойств.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

в противном случае конструктор копирования / перемещения нетривиален.

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

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

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

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

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

// 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's 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.

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

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

Вот как идет определение в стандартном тексте:

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

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

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

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

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

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

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

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

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

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

А давайте посмотрим несколько примеров.

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

struct StandardLayout2 {
    int x;
};

struct StandardLayout3 {
private: // both are private, so it's 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;
93 голосов
/ 16 декабря 2014

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

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

Агрегаты

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

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

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

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

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

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

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

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

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

, такой же, что и в C ++ 11.

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

Как отмечено в комментариях pod опирается на определение standard-layout , и это изменилось для C ++ 14, но это было в сообщениях о дефектах, которыебыли применены к C ++ 14 после факта.

Было три DR:

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

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

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

до это в C ++ 14 :

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

  • (3.1) имеетнет элементов нестатических данных типа класса нестандартной компоновки (или массива таких типов) или ссылок,
  • (3.2) не имеет виртуальных функций и виртуальных базовых классов,
  • (3.3) имеет одинаковое управление доступом для всех нестатических элементов данных,
  • (3.4) не имеет базовых классов нестандартной компоновки,
  • (3.5) имеет не более одного подобъекта базового классалюбого данного типа,
  • (3.6) имеет все нестатические члены-данные и битовые поля в классе и его базовые классы, впервые объявленные в tон же класс, а
  • (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) пуст.
44 голосов
/ 29 февраля 2012

Не могли бы вы разработать следующие правила:

Я попробую:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

31 голосов
/ 10 июня 2018

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

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

Агрегаты

C ++ 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&ltC&gt::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
Явные конструкторы по умолчанию запрещены
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
Запрещено наследование конструкторов
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


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

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

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

Изменения:

  1. В C ++14, для того чтобы класс был тривиальным, в классе не должно быть никаких конструкторов / операторов присваивания / перемещения, которые были бы нетривиальными.Однако тогда неявно , объявленный в качестве дефолтного конструктора / оператора, может быть нетривиальным и тем не менее определяет как удаленный, поскольку, например, класс содержит подобъект типа класса, который не может бытьскопированы / перемещены.Наличие такого нетривиального конструктора / оператора, определенного как удаленный, приведет к тому, что весь класс будет нетривиальным.Аналогичная проблема существовала с деструкторами.В C ++ 17 поясняется, что наличие такого конструктора / операторов не приводит к тому, что класс будет нетривиально копируемым, а следовательно, нетривиальным, и что тривиально копируемый класс должен иметь тривиальный, не удаляемый деструктор. DR1734 , DR1928
  2. C ++ 14 разрешеноКопируемый класс, следовательно, тривиальный класс, чтобы каждый конструктор / оператор копирования / перемещения был объявлен как удаленный. Если, например, class был также стандартной компоновкой, его можно было бы легально скопировать / переместить с помощью std::memcpy. Это было семантическим противоречием, потому что, определяя как удаленные все операторы конструктора / присваивания, создатель класса явно предполагал, что класс не может быть скопирован / перемещен, однако класс все еще удовлетворяет определению тривиально копируемого класса. Следовательно, в C ++ 17 у нас есть новое предложение, утверждающее, что тривиально копируемый класс должен иметь хотя бы один тривиальный, не удаленный (хотя и не обязательно общедоступный) оператор конструктора / присваивания копирования / перемещения. См N4148 , DR1734
  3. Третье техническое изменение касается аналогичной проблемы с конструкторами по умолчанию. В C ++ 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. Определение Стэнdard-layout никогда не позволял типу любого базового класса быть того же типа, что и первый нестатический элемент данных. Это сделано для того, чтобы избежать ситуации, когда элемент данных с нулевым смещением имеет тот же тип, что и любой базовый класс. Стандарт C ++ 17 предоставляет более строгое, рекурсивное определение «набора типов всех подобъектов не базового класса, которые гарантированно находятся в классе стандартной компоновки с нулевым смещением», чтобы запретить такие типы от того, чтобы быть типом любого базового класса. См. DR1672 , DR2120 .

Примечание: Комитет по стандартам C ++ предполагал, что вышеуказанные изменения, основанные на отчетах о дефектах, будут применяться к C ++ 14, хотя новый язык отсутствует в опубликованном стандарте C ++ 14. Это в стандарте C ++ 17.

8 голосов
/ 17 декабря 2018

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

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

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

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

struct X {
    X() = delete;
};

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

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

no user-explicit или унаследованные конструкторы

до

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

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

Это также обеспечивает эффект йо-йо в следующем примере:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

В C ++ 11/14 B был , а не агрегатом из-за базового класса, поэтому B{} выполняет инициализацию значения, которая вызывает B::B(), которая вызывает A::A(), вточка, где это доступно.Это было правильно сформировано.

В C ++ 17 B стал агрегатом, потому что были разрешены базовые классы, что сделало B{} агрегат-инициализацию.Для этого требуется инициализация copy-list-* A из {}, но вне контекста B, где он недоступен.В C ++ 17 это плохо сформировано (auto x = B(); было бы хорошо, хотя).

В C ++ 20 сейчас, из-за вышеуказанного изменения правила, B снова перестает бытьагрегат (не из-за базового класса, а из-за объявленного пользователем конструктора по умолчанию - даже если он по умолчанию).Итак, мы возвращаемся к рассмотрению конструктора B, и этот фрагмент становится правильно сформированным.

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

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

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

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

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

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

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

Point p{1, 2}; // error

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

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

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

...