Как читать:
Эта статья довольно длинная. Если вы хотите узнать об агрегатах и 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, потому что многие языковые функции, как вы видите, ведут себя по-разному для них.