Путаница в отношении исключений в конструкторах C ++ - PullRequest
1 голос
/ 06 мая 2019

Я нашел то, что выглядит как противоречивые ответы относительно исключений, создаваемых внутри конструкторов в C ++. Один из ответов в этой ссылке говорит о том, что если исключение выдается внутри конструктора, предполагается, что построение не завершено, и поэтому деструктор не вызывается. Но эта ссылка обсуждает концепцию RAII на примере мьютекса, созданного в конструкторе и очищенного в деструкторе. В нем говорится, что если в конструкторе создается мьютекс, а затем конструктор вызывает функцию, которая выдает исключение, а обработчик исключений не определен, то деструктор все равно будет вызван и мьютекс будет очищен. Какие я скучаю?

Ответы [ 2 ]

4 голосов
/ 06 мая 2019

Деструктор строящегося объекта не выполняется, но все его члены, которые были построены, разрушаются;например:

struct A {
   A(int x) { ... }
   ~A() { ... }
};

struct B {
   A a1, a2, a3;
   B() : a1(1), a2(2), a3(3) { ... }
   ~B() { ... }
};

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

Даже если исключение выдается внутри ... тела B(), все субобъекты A будут уничтожены путем вызова ~A, но все равно ~B вызываться не будет.

Только если конструктор B является завершенным , то вы получите настоящий B экземпляр, а затем при уничтожении будет вызван ~B для выполнения кода уничтожения.

1 голос
/ 06 мая 2019

Давайте посмотрим на этот фрагмент кода:

#include <iostream>
#include <stdexcept>

class A {
public:
    A() {
        std::cout << "A's constructor\n";
    }

    ~A() {
        std::cout << "A's destructor\n";
    }
};

class B {
public:
    B() {
        std::cout << "B's constructor - begin\n";
        throw std::runtime_error("Error");
        std::cout << "B's constructor - end\n";
    }

    ~B() {
        std::cout << "B's destructor\n";
    }

private:
    A a;
};

int main() {
    try {
        B b;
    } catch(const std::runtime_error& exc) {
        std::cerr << exc.what() << '\n';
    }
}

Вот вывод программы:

A's constructor
B's constructor - begin
A's destructor
Error

В общем, когда объект создается сначала, его конструкторы его полей вызываются, а затемконструктор для объекта выполнен.Для каждого успешно выполненного конструктора должен быть вызван деструктор.Деструкторы вызываются в обратном порядке.Если конструктор дает сбой, тогда не вызывается деструктор, но если во время конструирования объекта некоторые или все его поля конструируются, они будут уничтожены.

Возвращаясь к примеру, в созданной мной функции mainобъект класса B.Объект содержит член класса A.Когда объект b создан, сначала создаются его поля (в данном случае это элемент с именем a) - это первая строка вывода.Затем конструктор объекта b начинает выполнение (вторая строка вывода).Конструктор B вызывает исключение.Поскольку конструктор поля b (то есть конструктор a) уже успешно выполнен, деструктор a должен называться - третья строка вывода.Теперь конструктор b не успешно завершил свое выполнение, поэтому деструктор не будет вызываться для b.Последняя строка вывода - это результат обработки кода исключением в функции main.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...