Как работает RAII, когда конструктор создает исключение?
Я изучаю идиому RAII на С++ и как использовать интеллектуальные указатели.
В моем чтении я столкнулся с двумя вещами, которые, как мне кажется, противоречат друг другу.
Цитата из http://www.hackcraft.net/raii/:
... если объект-член с семантикой RAII был создан и исключение происходит до завершения конструктора, его деструктор будет вызван как часть разворачивания стека. Следовательно, объект, который управляет несколькими ресурсами, может гарантировать их очистку, даже если он полностью не сконструирован с использованием объектов-членов RAII.
Но цитируется из http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10:
Если конструктор генерирует исключение, деструктор объекта не запускается. Если ваш объект уже сделал что-то, что нужно отменить (например, выделение некоторой памяти, открытие файла или блокировка семафора), этот "материал, который нужно отменить" должен быть запомнен элементом данных внутри объекта.
И затем второй связанный источник рекомендует использовать интеллектуальные указатели для решения проблемы вещей, которые уже были выделены в конструкторе.
Итак, что на самом деле происходит в этих сценариях?
Ответы
Ответ 1
Вы неправильно понимаете первую цитату. Это не сложно, так как это сбивает с толку.
если объект-член с семантикой RAII был создан, и исключение происходит до завершения конструктора, тогда его деструктор будет вызван как часть разворачивания стека.
Что он говорит. Вот что это значит:
если объект-член с семантикой RAII был создан и исключение происходит во внешнем объекте до завершения конструктора внешнего объекта, затем объект-объект участника деструктор будет вызван как часть разворачивания стека.
Посмотрите разницу? Идея состоит в том, что объект-член завершил свой конструктор, но у владеющего типа этого не было. Он бросил где-то в свой конструктор (или конструктор другого члена, который инициализируется после этого). Это приведет к вызову деструктора всех его членов (все из тех, которые завершили строительство, то есть), но не его собственный деструктор.
Вот пример:
class SomeType
{
InnerType val;
public:
SomeType() : val(...)
{
throw Exception;
}
};
При создании экземпляра SomeType
он вызовет InnerType::InnerType
. Пока это не выбрасывается, он затем вводит конструктор SomeType
. Когда это произойдет, это приведет к уничтожению val
, вызывая InnerType::~InnerType
.
Ответ 2
Здесь нет противоречия; там только какая-то запутанная терминология используется в разных контекстах.
Если конструктор объекта генерирует исключение, то возникает следующее (при условии, что исключение поймано):
- Все локальные переменные в конструкторе вызываются деструкторами, освобождая все ресурсы, которые они приобрели (если они есть).
- Все прямые подобъекты объекта, у конструктора, создавшего исключение, будут вызывать их деструкторы, освобождая ресурсы, которые они приобрели (если они есть).
- Все базовые классы объекта, у которого созданный конструктор, будут вызывать их деструкторы (поскольку они были полностью построены до запуска конструктора производного класса)
- Будет проведена дополнительная очистка от вызывающего и т.д.
В результате любые ресурсы, которые управляются интеллектуальными указателями или другими объектами RAII, являющимися членами данных разрушаемого объекта, действительно будут очищены, но специализированный код для очистки в деструкторе объекта не будет срабатывать.
Надеюсь, это поможет!
Ответ 3
Эти два утверждения не противоречат друг другу, но первый имеет некоторый печальный язык. Когда построение какого-либо объекта бросает, его деконструктор не будет вызываться, но все объекты, принадлежащие этому объекту, будут разрушены их отдельными деконструкторами.
Таким образом, с помощью RAII и интеллектуальных указателей деструкторы для любых элементов указателя объекта будут вызываться независимо от деструктора объекта-объекта. Необработанные указатели не освобождают память, на которую они указывают, и ее необходимо удалить вручную. Если конструктор объекта-владельца не будет выгружать исходные указатели, он не будет освобожден. Это не может произойти с интеллектуальными указателями.