Как работает RAII, когда конструктор создает исключение? - программирование
Подтвердить что ты не робот

Как работает RAII, когда конструктор создает исключение?

Я изучаю идиому RAII на С++ и как использовать интеллектуальные указатели.

В моем чтении я столкнулся с двумя вещами, которые, как мне кажется, противоречат друг другу.

Цитата из http://www.hackcraft.net/raii/:

... если объект-член с семантикой RAII был создан и исключение происходит до завершения конструктора, его деструктор будет вызван как часть разворачивания стека. Следовательно, объект, который управляет несколькими ресурсами, может гарантировать их очистку, даже если он полностью не сконструирован с использованием объектов-членов RAII.

Но цитируется из http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10:

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

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

Итак, что на самом деле происходит в этих сценариях?

4b9b3361

Ответ 1

Вы неправильно понимаете первую цитату. Это не сложно, так как это сбивает с толку.

если объект-член с семантикой RAII был создан, и исключение происходит до завершения конструктора, тогда его деструктор будет вызван как часть разворачивания стека.

Что он говорит. Вот что это значит:

если объект-член с семантикой RAII был создан и исключение происходит во внешнем объекте до завершения конструктора внешнего объекта, затем объект-объект участника деструктор будет вызван как часть разворачивания стека.

Посмотрите разницу? Идея состоит в том, что объект-член завершил свой конструктор, но у владеющего типа этого не было. Он бросил где-то в свой конструктор (или конструктор другого члена, который инициализируется после этого). Это приведет к вызову деструктора всех его членов (все из тех, которые завершили строительство, то есть), но не его собственный деструктор.

Вот пример:

class SomeType
{
  InnerType val;
public:
  SomeType() : val(...)
  {
    throw Exception;
  }
};

При создании экземпляра SomeType он вызовет InnerType::InnerType. Пока это не выбрасывается, он затем вводит конструктор SomeType. Когда это произойдет, это приведет к уничтожению val, вызывая InnerType::~InnerType.

Ответ 2

Здесь нет противоречия; там только какая-то запутанная терминология используется в разных контекстах.

Если конструктор объекта генерирует исключение, то возникает следующее (при условии, что исключение поймано):

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

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

Надеюсь, это поможет!

Ответ 3

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

Таким образом, с помощью RAII и интеллектуальных указателей деструкторы для любых элементов указателя объекта будут вызываться независимо от деструктора объекта-объекта. Необработанные указатели не освобождают память, на которую они указывают, и ее необходимо удалить вручную. Если конструктор объекта-владельца не будет выгружать исходные указатели, он не будет освобожден. Это не может произойти с интеллектуальными указателями.