Что произойдет, если конструктор выдает исключение?

Будем ли мы получать UB? Я пробовал это:

#include <iostream>

struct B
{
    B(){ std::cout << "B()" << std::endl; }
    ~B(){ std::cout << "~B()" << std::endl; }
};

struct A
{
    B b;
    A(){ std::cout << "A()" << std::endl; throw std::exception(); }
    ~A(){ std::cout << "~A()" << std::endl; }
};

int main()
{
    A a;
}

дескриптор не был вызван как для A, так и B. Фактический вывод:

B()
A()
terminate called after throwing an instance of 'std::exception'
  what():  std::exception
bash: line 7: 21835 Aborted                 (core dumped) ./a.out

http://coliru.stacked-crooked.com/a/9658b14c73253700

Итак, всякий раз, когда конструктор бросает во время инициализации переменных области блока, получаем ли мы UB?

Ответ 1

Нет, создание исключения - лучший способ сообщить об ошибке во время создания объекта. (Так как нет возвращаемого значения, нет другого пути, кроме создания безголового объекта, что является плохим стилем в C++.)

От самого человека Бьярна Страуструпа: http://www.stroustrup.com/bs_faq2.html#ctor-exceptions

Re: "Но мой деструктор не был назван"

На самом деле. В C++ говорится, что время жизни объекта начинается, когда конструктор завершается. И это заканчивается прямо тогда, когда деструктор вызывается. Если ctor выбрасывает, то dtor не вызывается.

(Но вызываются dtors любых объектов-переменных-членов, чьи ctor уже выполнялись до завершения до этого ctor.)

Вы должны проконсультироваться со стандартом или хорошим учебником для более подробной информации, особенно связано с тем, что происходит, когда наследование вовлечено. Как правило, деструкторы вызываются в обратном порядке построения.

Ваш вопрос о том, почему "~ B" не был вызван в вашем конкретном коде, это потому, что вы не уловили исключение в main. Если вы измените свой код так, чтобы main перехватывал исключение, будет вызван "~ B()". Но когда генерируется исключение, которое не имеет перехватчика, реализация может завершить программу без вызова деструкторов или уничтожения статически инициализированных объектов.

Ссылка в C++ 11 стандарте (выделено мной):

15.5.1 The std::terminate() function [except.terminate]

1 В некоторых ситуациях обработка исключений должна быть прекращена для менее тонких методов обработки ошибок.

...

2 В таких случаях вызывается std::terminate() (18.8.3). В ситуации, когда соответствующий обработчик не найден, определяется реализацией, будет ли стек разматываться перед вызовом std::terminate().

В качестве дополнительного примечания, в общем случае с gcc и clang, ~B будет вызываться в любом случае в вашей примерной программе, в то время как с MSVC ~B вызываться не будет. Обработка исключений сложна, и стандарт позволяет авторам компиляторов экспериментировать с ними и выбирать, какую реализацию они считают лучшей в этом отношении, но они не могут выбирать неопределенное поведение.

Если для вашей программы действительно важно, чтобы деструкторы вызывались даже в этом случае, то вам следует убедиться, что вы ловите исключения в main, чтобы ваш код был переносимым (работающим на всех соответствующих компиляторах).

Ответ 2

Отбрасывание исключений в конструкторе является стандартным способом обработки ошибок и не является undefined. Если вы создаете конструктор, предполагается, что объект не был инициализирован правильно, поэтому его деструктор не вызывается.