Конструктор по-умолчанию для константного класса
Почему эта программа не компилируется?
#include <iostream>
struct A {
A() = default;
int x;
};
int main() {
const A a;
std::cout << a.x << '\n';
}
А вот эта компилируется?
#include <iostream>
struct A {
A();
int x;
};
A::A() = default;
int main() {
const A a;
std::cout << a.x << '\n';
}
И эта тоже работает:
#include <iostream>
struct A {
A() = default;
int x;
};
int main() {
const A a{};
std::cout << a.x << '\n';
}
Ошибка компиляции:
error: uninitialized 'const a'
Ответы (2 шт):
В первом случае A() = default; не производит инициализацию x. Класс остается тривиально инициализируемым. static_assert(std::is_trivially_constructible_v<A>);
Во втором сначала объявляется пользовательский конструктор по-умолчанию A();, который затем генерируется и инициализирует x. Класс уже не будет тривильно инициализируемым static_assert(not std::is_trivially_constructible_v<A>);
В третьем неинициализированные поля инициализируются на месте const A a{}; за счет использования синтаксиса direct list initialization.
Тривиальность конструктора тут не при чем. Если добавить к классу поле с нетривиальным конструктором (напримерstd::string), то std::is_trivially_constructible будет в любом случае возвращать false, а поведение не изменится.
У конструктора по умолчанию, автоматически сгенерированного компилятором (не наш случай), или объявленного как =default внутри тела класса (наш случай) есть особое свойство, которое нельзя повторить в самодельном конструкторе.
Поведение такого конструктора зависит от того, были ли использованы скобки () или {} при его вызове (value initialization), или нет (default initialization):
Если скобок нет, то он ведет себя как обычно.
Если скобки есть, то поля, которые иначе остались бы неинициализированными, зануляются (value initialization).
А если конструктор объявлен как =default снаружи тела класса, то у него такого специального поведения не будет. Насколько я знаю, он ничем не будет отличаться от пустого конструктора ({} вместо =default).
Так вот. В первом случае сработала защита от дурака, про которую я раньше не слышал. (В духе запрета на объявления вроде const int x; без инициализатора.)
[dcl.init]/7
...
A class typeTis const-default-constructible if default-initialization ofTwould invoke a user-provided constructor ofT(not inherited from a base class) or if(7.4) — each direct ... non-static data member
MofThas a default member initializer or, ifMis of class typeX(or array thereof),Xis const-default-constructible,
...
If a program calls for the default-initialization of an object of a const-qualified typeT,Tshall be a const-default-constructible class type or array thereof.
То есть ошибку вызывает default-инициализация (то есть без использования скобок () или {}, с ними была бы value-инициализация) константного экземпляра класса, у которого конструктор по умолчанию не user-provided (то есть сгенерирован компилятором автоматически, или отмечен как =default внутри тела класса), если у класса есть поля, котороые при таком раскладе остались бы неинициализированными.
В третьем случае x инициализируется нулем, и параграф выше не применяется (и то и другое - потому что для инициализации используются скобки).
Во втором случае x остается неинициализированным, но никакой защиты от этого нет.
Можно догадаться почему:
Во-первых, в общем случае сложно определить, присваивает ли конструктор какое-то значение полю или нет.
Во-вторых, определение конструктора могло быть спрятано в отдельную единицу трансляции, и хотя в здесь этого сделано не было, компилятор все равно не смотрит в тело конструктора. Предположительно для того, чтобы перемещение его в отдельную единицу трансляции не меняло поведение кода.