Цей FAQ стосується агрегатів та ПНД і охоплює такий матеріал:
- Що таке агрегати ?
- Що таке POD s (Звичайні старі дані)?
- Як вони пов'язані?
- Як і чому вони особливі?
- Які зміни для C ++ 11?
Цей FAQ стосується агрегатів та ПНД і охоплює такий матеріал:
Відповіді:
Ця стаття досить довга. Якщо ви хочете дізнатися як про агрегати, так і про PODS (Plain Old Data), знайдіть час і прочитайте їх. Якщо вас цікавлять саме агрегати, читайте лише першу частину. Якщо вас цікавлять лише ПОД, то спершу слід ознайомитись із визначенням, наслідками та прикладами агрегатів, а потім ви можете перейти до ПОД, але я все ж рекомендую прочитати першу частину повністю. Поняття агрегатів є важливим для визначення ПДР. Якщо ви виявите помилки (навіть незначні, включаючи граматику, стилістику, форматування, синтаксис тощо), будь ласка, залиште коментар, я відредагую.
Ця відповідь стосується 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] = {a1, a2, …, am};
якщо (m == n)
i- й елемент масиву ініціалізується з i
else, якщо (m <n)
перші m елементів масиву ініціалізовані з 1 , 2 , ..., a m та іншимиn - m
елементами є, якщо можливо, значенням ініціалізовано (див. нижче для пояснення терміна)
інше, якщо (m> n)
компілятор видасть помилку
ще (це той випадок, коли 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
Формальне визначення зі стандарту C ++ ( C ++ 03 9 §4 ) :
POD-структура - це сукупний клас, який не має нестатичних членів даних типу не-POD-структура, не-POD-об'єднання (або масив таких типів) або посилання, і не має визначеного користувачем оператора призначення копії та немає визначений користувачем деструктор. Аналогічно, підключення POD - це сукупне об'єднання, яке не має нестатичних членів даних типу не-POD-структура, не-POD-об'єднання (або масив таких типів) або посилання, і не має визначеного користувачем оператора призначення копії і немає визначеного користувачем деструктора. Клас POD - це клас, який є або структурою POD, або об'єднанням POD.
Ого, цей складніше розбирати, чи не так? :) Давайте залишимо профспілки (на тих же підставах, що і вище) і перефразовуємо трохи чіткіше:
Сукупний клас називається POD, якщо він не має визначеного користувачем оператора присвоєння копії та деструктора, і жоден з його нестатичних членів не є класом POD, масивом non-POD або посиланням.
Що означає це визначення? (Чи згадав я POD для стенду Plain Old Data ?)
Приклади:
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-класи є найближчими до C-структур. На відміну від них, POD можуть мати функції членів та довільні статичні елементи, але жоден з цих двох не змінює компонування пам'яті об'єкта. Отже, якщо ви хочете написати більш-менш портативну динамічну бібліотеку, яку можна використовувати з C та навіть .NET, вам слід спробувати зробити так, щоб усі експортовані функції приймали та повертали лише параметри POD-типів.
Термін експлуатації об'єктів типу не POD починається після закінчення конструктора і закінчується, коли деструктор закінчився. Для класів POD термін служби починається, коли сховище для об'єкта зайнято, і закінчується, коли це сховище звільнене або використане повторно.
Для об'єктів типів POD стандартно гарантується, що коли ви memcpy
вмістите ваш об'єкт у масив char або непідписаний 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 не буде прокладки. Іншими словами, якщо перший член стручка-CLASS A є тип T, ви можете сміливо reinterpret_cast
від A*
до T*
і отримати покажчик на перший елемент , і навпаки.
Список продовжується і продовжується…
Важливо зрозуміти, що саме таке ПОД, оскільки багато мовних особливостей, як бачите, поводяться по-різному.
private:
відповідно): struct A { int const a; };
тоді A()
воно добре сформоване, навіть якщо A
визначення конструктора за замовчуванням було б неправильним.
Стандартне визначення агрегату трохи змінилося, але воно все ще майже те саме:
Сукупність - це масив або клас (п. 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 - це несоюзний клас, який є і тривіальним класом, і класом стандартного макету, і не має нестатичних членів даних типу non-POD structure, un-POD об'єднання (або масив таких типів). Аналогічно, об'єднання POD - це об'єднання, яке є і тривіальним класом, і класом стандартного макета, і не має нестатичних членів даних типу не-POD структура, не-POD об'єднання (або масив таких типів). Клас POD - це клас, який є або структурою POD, або об'єднанням POD.
Давайте детально розглянемо кожну з цих двох властивостей окремо.
Trivial - це перше згадане вище властивість: тривіальні класи підтримують статичну ініціалізацію. Якщо клас можна тривіально скопіювати (набір тривіальних класів), нормально скопіювати його представлення на місці з такими речами memcpy
і очікувати, що результат буде однаковим.
Стандарт визначає тривіальний клас наступним чином:
Тривіально скопіюваний клас - це клас, який:
- не має конструкторів нетривіальних копій (12.8),
- не має конструкторів нетривіального переміщення (12.8),
- не має операторів призначення нетривіального копіювання (13.5.3, 12.8),
- не має операторів призначення нетривіального переміщення (13.5.3, 12.8) та
- має тривіальний деструктор (12.4).
Тривіальний клас - це клас, який має тривіальний конструктор за замовчуванням (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 окремо (якщо це лише одне з тривіального або стандартного макета).
Стандартна бібліотека має риси перевірки цих властивостей у заголовку <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;
Для посилання ми можемо звернутися до стандарту проекту 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 ( звичайні старі дані ) висвітлено в розділі 9
Класи, який говорить:
Структура POD 110 - це несоюзний клас, який є і тривіальним класом, і класом стандартного макету, і не має нестатичних членів даних типу non-POD Structure, non-POD об'єднання (або масив таких типів). Аналогічно, об'єднання POD - це об'єднання, яке є і тривіальним класом, і класом стандартного макета, і не має нестатичних членів даних типу не-POD структура, об'єднання, що не є POD (або масив таких типів). Клас POD - це клас, який є або структурою POD, або об'єднанням POD.
що є тим же формулюванням, що і C ++ 11.
Як зазначається в коментарях, струк покладається на визначення стандартного макета, і це змінилося для C ++ 14, але це було через повідомлення про дефекти, які були застосовані до C ++ 14 після факту.
ДР було:
Тож стандартний макет пішов із цього попереднього 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) має всі нестатичні члени даних і бітові поля в класі та його базові класи, спочатку оголошені в тому ж класі, і
- (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) порожня.
Ви можете, будь ласка, розробити такі правила:
Я спробую:
a) Класи стандартного макета повинні мати всі нестатичні члени даних з однаковим контролем доступу
Це просто: все не статичні дані повинні все бути public
, private
або protected
. Ви не можете мати деяких public
і деякихprivate
.
Міркування для них переходять до міркувань про те, щоб розрізняти "стандартний макет" і "не стандартний макет" взагалі. А саме, щоб надати компілятору свободу вибирати, як скласти речі в пам'ять. Йдеться не лише про vtable покажчики.
Ще коли вони стандартизували C ++ у 98 році, їм довелося в основному передбачити, як люди його застосовуватимуть. Хоча вони мали досить багато досвіду впровадження з різними смаками C ++, вони не були впевнені в речах. Тож вони вирішили бути обережними: надайте компіляторам якомога більше свободи.
Ось чому визначення POD у С ++ 98 настільки суворе. Це дало компіляторам C ++ велику широту розміщення членів для більшості класів. В основному, типи POD призначені для особливих випадків, про що ви спеціально писали з причини.
Коли над C ++ 11 працювали, вони мали набагато більше досвіду роботи з компіляторами. І вони зрозуміли, що ... Співавтори компіляторів C ++ справді ледачі. Вони мали всю цю свободу, але цього не робили нічого з цим .
Правила стандартного планування більш-менш кодифікують загальну практику: більшості компіляторів насправді не довелося багато змінювати, щоб взагалі щось реалізувати (за винятком, можливо, деяких матеріалів для відповідних ознак типу).
Зараз, коли мова зайшла про public
/ private
, все інакше. Свобода змінити порядок членів команди public
vs.private
насправді може мати значення для компілятора, особливо у налагодженні складок. А оскільки суть стандартного макета полягає в сумісності з іншими мовами, ви не можете мати макет відрізнятись від налагодження проти випуску.
Тоді є той факт, що це не дуже шкодить користувачеві. Якщо ви робите інкапсульований клас, шанси на те, що всі ваші учасники даних все private
одно будуть . Як правило, учасників публічних даних не виставляють на цілком капсульовані типи. Тож це буде лише проблемою для тих небагатьох користувачів, які хочуть зробити це, хто хоче цього поділу.
Тож це не велика втрата.
б) лише один клас у всьому дереві спадкування може мати нестатичні члени даних,
Причина цього повертається до того, чому вони знову стандартизували стандартний макет: звичайна практика.
Там немає НЕ звичайної практики , коли справа доходить до двох членів дерева спадкування , що на насправді зберігати речі. Одні ставлять базовий клас перед похідним, інші роблять це іншим способом. Яким способом ви замовляєте членів, якщо вони походять з двох базових класів? І так далі. Компілятори сильно розходяться з цих питань.
Крім того, завдяки правилу нуль / один / нескінченність, коли ти кажеш, що можеш мати два класи з членами, ти можеш сказати стільки, скільки хочеш. Для цього потрібно додати багато правил компонування, як впоратися з цим. Ви повинні сказати, як працює багатократне успадкування, які класи передають свої дані перед іншими класами і т. Д. Це дуже багато правил для дуже невеликої матеріальної вигоди.
Ви не можете зробити все, що не має віртуальних функцій та стандартного макета конструктора за замовчуванням.
і перший нестатичний член даних не може бути типу базового класу (це може порушити правила псевдоніму).
Я не можу реально говорити з цим. Я недостатньо освічений в правилах збитку C ++, щоб справді його зрозуміти. Але це має щось спільне з тим, що базовий член поділить ту саму адресу, що і сам базовий клас. Це є:
struct Base {};
struct Derived : Base { Base b; };
Derived d;
static_cast<Base*>(&d) == &d.b;
І це, мабуть, суперечить правилам C ++. Якимось чином.
Тим НЕ менше, вважають це: наскільки корисним може маючи можливість зробити це коли - небудь на насправді бути? Оскільки лише один клас може мати нестатичні члени даних, то він Derived
повинен бути цим класом (оскільки він має Base
як член). Тому Base
повинно бути порожнім (даних). А якщо Base
порожній, як і базовий клас ... навіщо взагалі мати його членами даних?
Оскільки Base
він порожній, він не має стану. Таким чином, будь-які нестатичні функції членів будуть робити те, що вони роблять, спираючись на їх параметри, а не на їх this
вказівник.
Тож знову: без великих втрат.
static_cast<Base*>(&d)
і &d.b
тим же Base*
типом, вони вказують на різні речі. Будь ласка, виправте мене.
Derived
повинен бути цей клас?
Derived
перший член був його базовим класом, він повинен мати дві речі: базовий клас та член . А оскільки лише один клас в ієрархії може мати членів (і все ще бути стандартним макетом), це означає, що його базовий клас не може мати членів.
Завантажте остаточний проект проекту C ++ 17 International Standard тут .
Агрегати
C ++ 17 розширює та розширює агрегати та ініціалізацію сукупності. Стандартна бібліотека також тепер включає std::is_aggregate
клас ознаки типу. Ось формальне визначення з розділів 11.6.1.1 та 11.6.1.2 (внутрішні посилання відсутні)
Сукупність - це масив або клас з
- не надані користувачем, явні або успадковані конструктори,
- відсутні приватні або захищені нестатичні члени даних,
- відсутні віртуальні функції, і
- відсутні віртуальні, приватні або захищені базові класи.
[Примітка: Агрегатна ініціалізація не дозволяє отримувати доступ до захищених та приватних членів або конструкторів базового класу. —Закінчити примітку]
Елементами сукупності є:
- для масиву, елементи масиву в порядку збільшення підпису, або
- для класу, прямі базові класи в порядку декларування, за якими прямі нестатичні члени даних, які не є члени анонімного союзу, в порядку декларації.
Що змінилося?
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
<< "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
<< " i1: " << c.i1 << " i2: " << c.i2
<< " j: " << c.j << " m.m: " << c.m.m << 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 (внутрішні посилання зникли):
Тривіально скопіюваний клас - це клас:
- де кожен конструктор копіювання, конструктор переміщення, оператор присвоєння копії та оператор присвоєння переміщення видалено або тривіально,
- який має щонайменше один не видалений конструктор копій, конструктор переміщення, оператор призначення копії, або перемістити оператор присвоєння, і
- у якого є тривіальний, не видалений деструктор.
Тривіальний клас - це клас, який можна скопіювати тривіально і має один або кілька конструкторів за замовчуванням, всі вони або тривіальні, або видалені, і принаймні один з них не видаляється. [Примітка. Зокрема, у тривіально копіюваного або тривіального класу немає віртуальних функцій або віртуальних базових класів. Кінцева примітка]
Зміни:
std::memcpy
. Це було смисловим протиріччям, оскільки, визначивши видаленими всі оператори конструктора / присвоєння, творець класу чітко задумав, що клас неможливо скопіювати / перемістити, але клас все-таки відповідав визначенню класу, який може бути скопійований. Отже, у C ++ 17 у нас є новий пункт, в якому йдеться про те, що тривіально копіюваний клас повинен мати принаймні один тривіальний, не видалений (хоча і не обов'язково загальнодоступний) конструктор копіювання / переміщення конструктора / призначення. Див. N4148 , DR1734Класи стандартного планування
Визначення стандартного макета також було перероблено для усунення звітів про дефекти. Знову зміни були технічного характеру. Ось текст зі стандарту (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) Це гарантує, що два суб’єкти, які мають один і той же тип класу і належать до одного і того ж самого похідного об'єкта, не виділяються за однією адресою.
Зміни:
Примітка: Комітет стандартів C ++ призначив вищезазначені зміни, засновані на повідомленнях про дефекти, застосувати до C ++ 14, хоча нова мова відсутня у опублікованому стандарті C ++ 14. Це в стандарті C ++ 17.
Після решти чіткої теми цього питання значення та використання агрегатів продовжує змінюватися з кожним стандартом. На горизонті відбувається кілька ключових змін.
У C ++ 17 цей тип як і раніше є сукупним:
struct X {
X() = delete;
};
А значить, X{}
все ще компілюється, тому що це сукупна ініціалізація - не виклик конструктора. Дивіться також: Коли приватний конструктор не є приватним конструктором?
У C ++ 20 обмеження зміниться:
не надані користувачем
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{}
агрегатизацію. Для цього потрібна ініціалізація копіювання списку A
з {}
, але поза межами контексту B
, де це недоступно. У C ++ 17 це неправильно формується (auto x = B();
хоч було б добре).
У C ++ 20 зараз, через вищезазначену зміну правила, B
знову перестає бути агрегатом (не через базовий клас, а через оголошений користувачем конструктор за замовчуванням - навіть за умови дефолту). Тож ми повертаємось до перегляду B
конструктора, і цей фрагмент стає добре сформованим.
Поширена проблема, яка виникає, полягає у бажанні використовувати emplace()
конструктори-стилі з агрегатами:
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
Це не працює, оскільки emplace
спробує ефективно виконати ініціалізацію X(1, 2)
, що не вірно. Типове рішення полягає в тому, щоб додати конструктор X
, але за допомогою цієї пропозиції (в даний час він працює через Core) агрегати будуть ефективно синтезувати конструктори, які роблять правильно - і поводяться як звичайні конструктори. Вищевказаний код буде складено як є в C ++ 20.
У C ++ 17 це не компілюється:
template <typename T>
struct Point {
T x, y;
};
Point p{1, 2}; // error
Користувачі повинні написати власний посібник з вирахування для всіх сукупних шаблонів:
template <typename T> Point(T, T) -> Point<T>;
Але оскільки це в певному сенсі "очевидна справа", і це в основному лише котельня, мова зробить це за вас. Цей приклад буде складено в C ++ 20 (без потреби в наданому користувачем посібнику щодо вирахування).