RAII и члены, если конструктор бросает? - PullRequest
0 голосов
/ 29 февраля 2020

Ранее я работал в условиях, когда исключения были отключены, а неудачное выделение памяти означает, что мы убиваем программу. Теперь, работая с исключениями, я задаюсь вопросом о точной семантике следующего:

class Foo {
  std::unique_ptr<Bar> x;
  std::unique_ptr<Bar> y;
 public:
  Foo(): x{new Bar}, y{new Bar} {}
};

Мой вопрос: что происходит, когда new Bar бросает, когда выделяется y? Я бы предположил, что деструктор x вызывается так, что первое распределение очищается. Как язык гарантирует это? Кто-нибудь знает цитату из стандарта, которая объясняет точную семантику?

Ответы [ 3 ]

4 голосов
/ 29 февраля 2020

Да, все полностью построенные члены будут уничтожены. Ваш объект не останется в каком-либо «полуживом» состоянии. Никаких утечек памяти не произойдет.

[except.ctor]/3: если инициализация или уничтожение объекта, отличного от делегирующего конструктора, завершается исключением, деструктор вызывается для каждого непосредственных подобъектов объекта и, для полного объекта, подобъектов виртуального базового класса, инициализация которых завершена ([dcl.init]) [..] Подобъекты уничтожаются в обратном порядке завершения их конструкция. [..]

Мы можем продемонстрировать это сами:

#include <memory>
#include <iostream>

struct Bar
{
    Bar(const char* name, bool doThrow = false) : m_name(name)
    {
        if (doThrow)
        {
            std::cout << name << ": Bar() throwing\n";
            throw 0;
        }

        std::cout << name << ": Bar()\n";
    }

    ~Bar() { std::cout << m_name << ": ~Bar()\n"; }

private:
    const char* m_name;
};

class Foo {
  std::unique_ptr<Bar> x;
  std::unique_ptr<Bar> y;
 public:
  Foo(): x{new Bar("A")}, y{new Bar("B", true)} {}
};

int main()
{
    try
    {
        Foo f;
    }
    catch (...) {}
}

// g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
// A: Bar()
// B: Bar() throwing
// A: ~Bar()

( живая демонстрация )

Фактически это одно из главных преимуществ так называемых «умных указателей»: исключение безопасности. Если бы x необработанный указатель, вы бы слили то, на что он указывал, потому что уничтожение необработанного указателя ничего не делает. С исключительной безопасностью вы можете иметь RAII; без него удачи.

3 голосов
/ 29 февраля 2020

Если вас беспокоит то, что два выражения new Bar, чередующиеся и выбрасываемые до инициализации дескрипторов для хранения того, что они означают, стандарт не позволяет этого.

Сначала в [intro .execution]

12 Полное выражение

  • init-декларатор или mem-initializer, включая составляющие выражения инициализатора,

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

Не вдаваясь в подробности, x{new Bar} и y{new Bar} в целом рассматриваются как то, что в стандарте считается "полным выражением" (даже если они не являются выражениями грамматически ). Два параграфа, которые я цитировал, указывают, что либо полная инициализация x (которая включает в себя new Bar) должна произойти первой, либо вся инициализация y должна произойти первой. Мы знаем из [class.base.init]

13,3 - Затем элементы данных c, не относящиеся к состоянию, инициализируются в том порядке, в котором они были объявлено в определении класса (опять же, независимо от порядка инициализации mem).

Итак, x инициализируется полностью, а затем y. Таким образом, даже если new Bar throws при инициализации y, x уже владеет ресурсом, который он должен хранить. В этом случае, когда выдается исключение, словосочетание в [exc.ctor] parageph 3 будет применяться к полностью построенному x, и оно будет уничтожено, освобождая ресурс.

0 голосов
/ 29 февраля 2020

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

https://eel.is/c++draft/basic.life#def: время жизни описывает правила жизни.

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