• Чтобы сделать деструктор виртуальным и при этом генерируемым компилятором.
• Чтобы сгенерировать специальный вариант копирующего конструктора, например, принимающий параметр по неконстантной ссылке (по умолчанию генерируется конструктор, принимающий константную ссылку).
• Чтобы воспользоваться специальными свойствами сгенерированных компилятором функций, который теряются, если вы сами пишете реализацию. Подробнее об этом чуть ниже.
Умалчиваемые функции объявляются путем добавления спецификатора = default
, например:
class Y {
private:
Y() = default; ←
Изменяем видимость
public:
Y(Y&) = default; ←
Принимаем не-const ссылку
T& operator=(const Y&) = default;←┐
объявляем умалчиваемой
│
для документирования
protected:
virtual ~Y() = default; ←
Изменяем видимость и добавляем virtual
};
Выше я упомянул, что сгенерированные компилятором функции обладают специальными свойствами, которые невозможно получить от версии, написанной пользователем. Самое существенное отличие заключается в том, что сгенерированная компилятором функция может быть тривиальной . Отсюда вытекает ряд следствий.
• Объекты с тривиальными копирующим конструктором, копирующим оператором присваивания и деструктором можно копировать с помощью memcpy
или memmove
.
• Литеральные типы, используемые в constexpr
-функциях (см. раздел А.4) обязаны обладать тривиальными конструктором, копирующим конструктором и деструктором.
• Классы с тривиальными конструктором по умолчанию, копирующим конструктором, копирующим оператором присваивания и деструктором можно использовать в объединении ( 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
Это странное свойство распространяется также на базовые классы и члены классов. Если в классе имеется сгенерированный компилятором конструктор по умолчанию, и каждый член самого класса и всех его базовых классов также имеет сгенерированный компилятором конструктор по умолчанию, то переменные-члены самого класса и его базовых классов, принадлежащие встроенным типам, также будут иметь неопределенное значение или будут инициализированы нулями в зависимости от того, вызывался ли явно для внешнего класса его конструктор по умолчанию.
У этого замысловатого и потенциально чреватого ошибками правила есть тем не менее применения, а, если вы пишете конструктор по умолчанию самостоятельно, то это свойство утрачивается; данные-члены (например, а
) либо всегда инициализируются (коль скоро вы указали значение или явно вызвали конструктор по умолчанию), либо вообще не инициализируются (если вы этого не сделали):
Читать дальше