Если конструктор не может построить корректный объект, выбросьте исключение
Причина
Оставленный за собой недопустимый объект — путь к неприятностям.
Пример
class X2 {
FILE* f;
// ...
public:
X2(const string& name)
:f{fopen(name.c_str(), "r")}
{
if (!f) throw runtime_error{"could not open" + name};
// ...
}
void read(); // читать из f
// ...
};
void f()
{
X2 file {"Zeno"}; // выбросит, если файл не открыт
file.read(); // хорошо
// ...
}
Плохой пример
class X3 { // плохо: конструктор оставляет за собой недопустимый объект
FILE* f; // вызовите is_valid() перед любой другой функцией
bool valid;
// ...
public:
X3(const string& name)
:f{fopen(name.c_str(), "r")}, valid{false}
{
if (f) valid = true;
// ...
}
bool is_valid() { return valid; }
void read(); // читать из f
// ...
};
void f()
{
X3 file {"Heraclides"};
file.read(); // крах или плохое чтение!
// ...
if (file.is_valid()) {
file.read();
// ...
}
else {
// ... обработать ошибку ...
}
// ...
}
Примечание
Для определения переменной (например, в стеке или как члена другого объекта) нет явного вызова функции, из которого можно было бы вернуть код ошибки. Оставленный за собой недопустимый объект и полагание на то, что пользователи будут последовательно проверять функцию is_valid() перед использованием, это утомительно, подвержено ошибкам и неэффективно.
Исключение
Существуют области, такие как некоторые жёсткие системы реального времени (подумайте об управлении самолётом), где (без дополнительной поддержки инструментов) обработка исключений недостаточно предсказуема с точки зрения времени. Там техника is_valid() должна быть использована. В таких случаях проверяйте is_valid() последовательно и немедленно, чтобы симулировать RAII.
Альтернатива
Если вас соблазняет использовать какой-то «пост-конструкторный инициализацион» или «двухэтапный инициализацион» идиом, постарайтесь этого не делать. Если вы действительно должны, посмотрите на функции-фабрики.
Примечание
Одна из причин, по которой люди использовали функции init() вместо выполнения работы инициализации в конструкторе, была необходимость избежать дублирования кода. Делегирующие конструкторы и инициализация членов по умолчанию делают это лучше. Другая причина была в отложении инициализации до момента, когда объект нужен; решением для этого часто является не объявлять переменную до тех пор, пока её нельзя правильно инициализировать.
Применение
???