Убедитесь, что копируемый класс имеет конструктор по умолчанию
Причина
То есть убедитесь, что если конкретный класс копируемый, то он также удовлетворяет остальным требованиям "полурегулярности".
Много средств языка и библиотек полагаются на конструкторы по умолчанию для инициализации своих элементов, например T a[10] и std::vector<T> v(10). Конструктор по умолчанию часто упрощает задачу определения подходящего состояния после перемещения для типа, который также копируемый.
Пример
class Date { // ПЛОХО: нет конструктора по умолчанию
public:
Date(int dd, int mm, int yyyy);
// ...
};
vector<Date> vd1(1000); // здесь нужен конструктор по умолчанию Date
vector<Date> vd2(1000, Date{7, Month::October, 1885}); // альтернатива
Конструктор по умолчанию генерируется автоматически только если нет пользовательского объявленного конструктора, поэтому инициализировать вектор vd1 в примере выше невозможно. Отсутствие значения по умолчанию может вызвать неожиданные ситуации для пользователей и усложнить его использование, поэтому если его можно разумно определить, это следует сделать.
Дата была выбрана для стимулирования размышлений: Нет "естественной" даты по умолчанию (Большой взрыв слишком далеко в прошлом, чтобы быть полезным для большинства людей), поэтому этот пример нетривиален. {0, 0, 0} не является допустимой датой в большинстве календарных систем, поэтому выбор этого значения означал бы введение чего-то вроде NaN в числах с плавающей точкой. Однако большинство реальных классов Date имеют "первую дату" (например, 1 января 1970 года является популярным выбором), поэтому сделать это значением по умолчанию обычно тривиально.
class Date {
public:
Date(int dd, int mm, int yyyy);
Date() = default; // [См. также](/roadmap/cpp/guidelines/c-45)
// ...
private:
int dd {1};
int mm {1};
int yyyy {1970};
// ...
};
vector<Date> vd1(1000);
Примечание
Класс с членами, у всех из которых есть конструкторы по умолчанию, неявно получает конструктор по умолчанию:
struct X {
string s;
vector<int> v;
};
X x; // означает X{ { }, { } }; то есть пустую строку и пустой вектор
Будьте осторожны: встроенные типы не инициализируются должным образом конструктором по умолчанию:
struct X {
string s;
int i;
};
void f()
{
X x; // x.s инициализируется пустой строкой; x.i не инициализирован
cout << x.s << ' ' << x.i << '\n';
++x.i;
}
Статически выделенные объекты встроенных типов по умолчанию инициализируются до 0, но локальные встроенные переменные — нет. Будьте осторожны: ваш компилятор может инициализировать локальные встроенные переменные по умолчанию, тогда как оптимизированная сборка не будет. Таким образом, код, подобный примеру выше, может казаться работающим, но он полагается на неопределённое поведение. Если вы хотите инициализацию, явная инициализация по умолчанию может помочь:
struct X {
string s;
int i {}; // инициализировать по умолчанию (до 0)
};
Примечания
Классы, которые не имеют разумного конструктора по умолчанию, обычно также не копируемы, поэтому они не попадают под это руководство.
Например, базовый класс не должен быть копируемым и, следовательно, не обязательно нуждается в конструкторе по умолчанию:
// Shape — абстрактный базовый класс, не копируемый тип.
// Может быть, может быть, может не быть конструктора по умолчанию.
struct Shape {
virtual void draw() = 0;
virtual void rotate(int) = 0;
// =delete copy/move functions
// ...
};
Класс, который должен получить предоставляемый вызывающей стороной ресурс во время конструкции, часто не может иметь конструктор по умолчанию, но он не попадает под это руководство, потому что такой класс обычно и не копируемый:
// std::lock_guard не является копируемым типом.
// У него нет конструктора по умолчанию.
lock_guard g {mx}; // охранять mutex mx
lock_guard g2; // ошибка: ничего не охранять
Класс, который имеет "специальное состояние", которое должно обрабатываться отдельно от других состояний функциями-членами или пользователями, вызывает дополнительную работу (и скорее всего больше ошибок). Такой тип может естественным образом использовать специальное состояние как значение, созданное конструктором по умолчанию, копируемо оно или нет:
// std::ofstream не является копируемым типом.
// Но он действительно имеет конструктор по умолчанию
// который сочетается со специальным состоянием "не открыт".
ofstream out {"Foobar"};
// ...
out << log(time, transaction);
Аналогичные типы со специальным состоянием, которые копируемы, такие как копируемые умные указатели с особым состоянием "==nullptr", должны использовать специальное состояние как значение, созданное конструктором по умолчанию.
Однако предпочтительнее иметь конструктор по умолчанию, устанавливающий осмысленное состояние, такое как "" в std::string и {} в std::vector.
Применение
- Пометить классы, которые копируемы через
=без конструктора по умолчанию - Пометить классы, которые сравниваются с
==но не копируемы