У меня есть класс, который может вызвать исключение в своем конструкторе. Как я могу объявить экземпляр этого класса в блоке try / catch, в то же время делая его доступным в нужной области?
try { MyClass lMyObject; }
catch (const std::exception& e) { /* Handle constructor exception */ }
lMyObject.DoSomething(); // lMyObject not in scope!
Есть ли альтернативный способ сделать это, уважая RAII идиома?
Я бы предпочел не использовать init()
метод двухфазного строительства. Единственное, что я мог придумать, было:
MyClass* lMyObject;
try { lMyObject = new MyClass(); }
catch (const std::exception& e) { /* Handle constructor exception */ }
std::shared_ptr<MyClass> lMyObjectPtr(lMyObject);
lMyObjectPtr->DoSomething();
Работает хорошо, но я не доволен необработанным указателем в области видимости и косвенным указателем. Это просто еще одна бородавка C ++?
Если конструктор выбрасывает, это означает, что объект не удалось инициализировать и, следовательно, он не смог начать свое существование.
MyClass* lMyObject;
try { lMyObject = new MyClass(); }
catch (std::exception e) { /* Handle constructor exception */ }
В приведенном выше случае, если конструктор выдает исключение, lMyObject
остается неинициализированным, другими словами, указатель содержит неопределенное значение.
См классика Сбои конструктора для подробного объяснения:
Мы могли бы обобщить модель конструктора C ++ следующим образом:
Или:
(a) Конструктор обычно возвращается, достигнув своего конца или оператора возврата, и объект существует.
Или же:
(b) Конструктор выходит, выдав исключение, и объект не только не существует, но и никогда не существовал.
Других возможностей нет.
Лучший способ написания вашего кода это:
MyClass lMyObject;
lMyObject.DoSomething();
Нет попыток, ловит или указатели.
Если конструктор выбрасывает, DoSomething не может быть вызван. Что правильно: если конструктор бросил, то объект никогда не был построен.
И, что важно, не перехватывайте (и даже не перехватывайте / перебрасывайте) исключения, если у вас нет ничего общего с ними. Позвольте исключениям делать свою работу и волноваться, пока что-то, что знает, как обращаться с ними, не сможет выполнить свою работу.
Конструкторы предназначены для приведения объекта в согласованное состояние и установления инвариантов класса. Разрешение исключить исключение конструктора означает, что что-то в этом конструкторе не удалось завершить, поэтому теперь объект находится в каком-то неизвестном странном состоянии (что может теперь также включать утечки ресурсов). Поскольку конструктор объекта не завершен, компилятор также не будет вызывать его деструктор. Возможно, вам нужно перехватить исключение внутри конструктора. Предполагая, что он не переброшен, это заставит конструктор завершить выполнение, и объект теперь полностью сформирован.
Вам не нужно использовать shared_ptr
использовать unique_ptr
:
std::unique_ptr<MyClass> pMyObject;
try { pMyObject.reset(new MyClass()); }
catch (std::exception &e) { /* Handle constructor exception */ throw; }
MyClass &lMyObject = *pMyObject;
lMyObject.DoSomething();
Очевидно, что вы несете ответственность за то, чтобы программа не провалилась catch
блок без инициализации pMyObject
или выход из функции (например, через return
или же throw
).
Если доступно, вы можете использовать Boost.Optional, чтобы избежать использования кучи памяти:
boost::optional<MyClass> oMyObject;
try { oMyObject.reset(MyClass()); }
catch (std::exception &e) { /* Handle constructor exception */ throw; }
MyClass &lMyObject = *oMyObject;
lMyObject.DoSomething();
Вы можете настроить конструктор копирования MyClass так, чтобы он принимал ввод мусора, тем самым эффективно объявляя указатель вместе с объявлением объекта. Тогда вы могли бы вызов вручную конструктор по умолчанию в блоке try:
MyClass lMyObject(null); // calls copy constructor
try {
new (lMyObject) MyClass(); // calls normal constructor
}
catch (const std::exception& e) { /* Handle constructor exception */ }
lMyObject.DoSomething();