Гайдлайны по современному C++ всячески намекают, а иногда напрямую советуют: следуюйте "правилу нуля" (rule of zero) для ваших классов, и структур и будет вам счастье! Используйте инициализацторы по умолчанию! C++20 улучшил поддержку структур-аггрегатов, так что не надо писать вручную конструкторы там где это не надо... Но legacy код существут, его затратно переписывать... А также существуют legacy разработчики, которые застряли в C++98...
Так что в старых кодовых базах можно встретить что-нибудь такое:
// Point.hpp
class Point2D {
public:
Point2D(int _x, int _y);
// Раз добавили какой-то конструктор,
// нужно добавить и конструктор по умолчанию
Point();
int x;
int y;
};
// Некоторые разработчики как мантру твердят, что
// определение любых функций всегда нужно выносить
// в компилируемый .cpp файл. Даже коротких.
// Point.cpp
Point2D::Point2D(int _x, int _y) : x {_x}, y {_y} {}
// И даже такие!
Point::Point2D() = default;
Делать так в современном C++ крайне не рекомендуется. Не только из-за обилия бессмысленного бойлерплейта, но и из-за риска получить неинициализированные поля и неопределенное поведение вместе с ними.
Инициализация в C++ -- невероятно сложная тема из-за обилия терминологии, переопределенного синтаксиса и вариативности, чтоб удовлетворить все мыслимые и немыслимые возможности. А также из-за множества особых случаев и исключений. И с подобным устаревшим подходом к описанию конструкторов как раз связано одно из таких исключений.
Пусть нам все-таки очень нужно иметь конструкторы для точки
И мы их определили в составе объявления класса
class Point2D {
public:
Point2D(int _x, int _y) : x {_x }, y {_y} {}
// Раз добавили какой-то конструктор,
// нужно добавить и конструктор по умолчанию
Point2D() = default;
int x;
int y;
};
И мы создаем точку, инициализированную по умолчанию с помощью фигурных скобок, как рекомендуется в современном C++
int main() {
Point2D a {};
return a.x;
}
Стандарт гарантирует, что произойдет zero initialization.
Потому как в классе из тривиальных типов без инициализаторов Point2D() = default
определил тривиальный конструктор по умолчанию. Так что все здорово. Никаких неинициализированных полей.
Но стоит нам вынести определение конструктора по умолчанию за пределы объявления класса
class Point2D {
public:
Point2D(int _x, int _y) : x {_x }, y {_y} {}
Point2D();
int x;
int y;
};
Point2D::Point2D() = default;
Как все резко поменяется! Теперь это уже нетривиальный конструктор. А значит инициализация фигурными скобками должна вызвать его вместо zero initialization.
И поля x
, y
останутся неинициализированными. Ведь мы их не инициализировали.
struct Bad {
int x;
Bad();
};
Bad::Bad() = default;
struct Good {
int x;
Good() = default;
};
int main() {
Bad a {};
Good b {};
return a.x + b.x;
}
При компиляции GCC c -std=c++26 -O3 -Wall -Wextra -Wpedantic -Wuninitialized
Мы получим предупреждение
<source>:15:14: warning: 'a.Bad::x' is used uninitialized [-Wuninitialized]
15 | return a.x + b.x;
Стоит отметить, что без оптимизаций, ни GCC 14, ни Clang 18 предупреждений не выдают.
Ну хорошо. Класс для 2D точки это все-таки отличный кандидат, чтоб просто использовать аггрегаты и списки инициализации и не думать.
Да. Делайте так!
struct Point2D {
int x = 0;
int y = 0;
};
Я также встречал эту проблему и в более сложных случаях:
Был класс для логгирования:
class Logger {
public:
Logger(std::string log_group)
Logger(); // определен как Logger::Logger() = default в .cpp файле
private:
// Это поле было в классе давно. У строк есть конструктор по умолчанию
// инициализатор не обязателен
std::string log_group;
};
В какой-то момент было решено добавить поле для контроля максимальной длины строки
class Logger {
public:
Logger(std::string log_group, size_t limit)
Logger();
private:
std::string log_group;
size_t limit; // Неопытный программист,
// которому поручили задачу, по аналогии добавил поле без инициализатора
};
Все компилируется, но логгер по умолчанию перестает работать, а = default
сбивает программиста с толку.
Инициализируйте поля явно! Всегда, кроме случаев, когда инициализация действительно становится проблемой для производительности.