• Классы с тривиальными конструктором по умолчанию, копирующим конструктором, копирующим оператором присваивания и деструктором можно использовать в объединении (union
), в котором определены пользовательские конструктор и деструктор.
• Классы с тривиальными конструктором копирующим оператором присваивания можно использовать вместе с шаблонным классом std::atomic<>
(см. раздел 5.2.6), то есть передавать значения такого типа атомарным операциям.
Одного объявления функции со спецификатором = default
недостаточно, чтобы сделать ее тривиальной, для этого класс должен удовлетворять всем прочим условиям, при которых соответствующая функция будет тривиальной. Однако явно написанная пользователем функция не будет тривиальной
Второе различие между классами с функциями, сгенерированными компилятором и написанными пользователем, заключается в том, что класс без написанных пользователем конструкторов может быть
struct aggregate {
aggregate() = default;
aggregate(aggregate const&) = default;
int a;
double b;
};
aggregate x={42, 3.141};
В данном случае x.a
инициализируется значением 42
, a x.b
— значением 3.141
.
Третье различие малоизвестно и относится только к конструктору по умолчанию, да и то лишь в классах, удовлетворяющих определенному условию. Рассмотрим такой класс:
struct X {
int а;
};
Если экземпляр класса X
создается без инициализатора, то содержащееся в нем значение (а
) типа int
X x1; ←
значение x1.a не определено
С другой стороны, если инициализировать экземпляр X
путем явного вызова конструктора по умолчанию, то он получит значение 0:
X x2 = X(); ←
x2.а == 0
Это странное свойство распространяется также на базовые классы и члены классов. Если в классе имеется сгенерированный компилятором конструктор по умолчанию, и каждый член самого класса и всех его базовых классов также имеет сгенерированный компилятором конструктор по умолчанию, то переменные-члены самого класса и его базовых классов, принадлежащие встроенным типам, также будут иметь неопределенное значение или будут инициализированы нулями в зависимости от того, вызывался ли явно для внешнего класса его конструктор по умолчанию.
У этого замысловатого и потенциально чреватого ошибками правила есть тем не менее применения, а, если вы пишете конструктор по умолчанию самостоятельно, то это свойство утрачивается; данные-члены (например, а
) либо всегда инициализируются (коль скоро вы указали значение или явно вызвали конструктор по умолчанию), либо вообще не инициализируются (если вы этого не сделали):
X::X() : а() {} ←
всегда а == 0
X::X() : а(42) {} ←
всегда а == 42
X::X() {} ←
(1)
Если инициализация а
при конструировании X
не производится (как в третьем примере (1)), то a
остается неинициализированным для нестатических экземпляров X
и инициализируется нулем для экземпляров X
со статическим временем жизни.
Обычно, если вы вручную напишете хотя бы один конструктор, то компилятор не станет генерировать конструктор по умолчанию. Стало быть, если он вам все-таки нужен, его придётся написать самостоятельно, а тогда это странное свойство инициализации теряется. Однако явно объявив конструктор умалчиваемым, вы можете заставить компилятор сгенерировать конструктор по умолчанию и сохранить это свойство:
X::X() = default;
Это свойство используется в атомарных типах (см. раздел 5.2), в которых конструктор по умолчанию явно объявлен умалчиваемым. У таких типов начальное значение не определено, если только не выполняется одно из следующих условий: (а) задан статический класс памяти (тогда значение инициализируется нулем); (b) для инициализации нулем явно вызван конструктор по умолчанию; (с) вы сами явно указали начальное значение. Отметим, что в атомарных типах конструктор для инициализации значением объявлен как constexpr
(см. раздел А.4), чтобы разрешить статическую инициализацию.
А.4. constexpr
-функции
Целые литералы, например 42
, — это 23*2-4
. Частью константного выражения могут быть также const
-переменные любого целочисленного типа, которые сами инициализированы константным выражением:
const int i = 23;
const int two_i = i * 2;
const int four = 4;