Если конструктор объекта выбрасывает исключение, какие части объекта будут уничтожены?
Если конструктор выбрасывает исключение, сам объект считается не сконструированным, поэтому его деструктор не вызывается. Уже полностью сконструированные нестатические члены и базовые подобъекты уничтожаются в обратном порядке их инициализации.
Это позволяет использовать RAII: ресурсы, которыми владеют уже созданные члены объекта, освобождаются даже при ошибке в конструкторе.
Исключения в C++ должны безопасно прерывать обычный поток выполнения и передавать управление обработчику выше по стеку. Для этого язык связывает разрушение уже созданных объектов с процессом раскрутки стека.
Такой подход поддерживает RAII: ресурс помещают во вспомогательный объект, а его деструктор освобождает ресурс независимо от того, завершился конструктор успешно или выбросил исключение.
Конструктор может успеть выделить ресурс или создать несколько членов, а затем обнаружить ошибку. Если при этом не уничтожать уже созданные части объекта, возникнут утечки памяти, незакрытые файлы или оставшиеся блокировки.
Нельзя просто вызвать деструктор всего объекта: его тело конструктора не завершилось, а часть подобъектов могла вообще не быть создана. Поэтому C++ уничтожает только те базовые подобъекты и члены, которые успели успешно сконструироваться.
Порядок действий такой:
В результате будут напечатаны second, затем first; Object напечатан не будет. Член second уничтожается первым, потому что он был создан позже first.
Если исключение возникает при конструировании самого члена, деструктор этого незавершённого члена не вызывается, но ранее созданные члены уничтожаются. Если исключение возникает в конструкторе базового класса, уже созданные до него подобъекты также корректно разрушаются.
Для объекта, созданного выражением new, при исключении из конструктора дополнительно вызывается соответствующая функция освобождения памяти. Поэтому обычно не требуется вручную освобождать память в месте вызова new при обработке такого исключения.
Практическое правило: каждый ресурс, захваченный конструктором, должен находиться во владении уже созданного RAII-объекта или быть освобождён до выбрасывания исключения. Сырые ресурсы, хранящиеся в полях без автоматического владельца, повышают риск утечки.
Класс сетевого соединения сначала создаёт объект блокировки, затем открывает сокет. Открытие сокета может завершиться ошибкой.
Вариант с ручным управлением ресурсами требует тщательно освобождать блокировку и сокет на каждой ветви ошибки. Такой код легко сломать при добавлении нового ресурса или нового места выбрасывания исключения.
Вариант с полями-объектами-владельцами автоматически освобождает уже созданные ресурсы при исключении. Его недостаток — необходимость подобрать или написать корректные RAII-обёртки, но это обычно оправдано меньшим количеством ошибкоопасного кода.
Выбранное решение — хранить блокировку и сокет в отдельных RAII-классах. Если открытие сокета завершается исключением, деструктор блокировки вызывается автоматически, а незавершённый объект соединения не считается созданным.
Вызывается ли деструктор объекта, если исключение выброшено из его конструктора?
Нет. Объект не считается полностью сконструированным, поэтому его деструктор не вызывается. Однако деструкторы уже созданных членов и базовых подобъектов вызываются.
В каком порядке уничтожаются члены при исключении из конструктора?
В обратном порядке их фактической инициализации, а не в обратном порядке записей в списке инициализации. Фактический порядок определяется объявлением членов в классе. Это важно, поскольку зависимость членов от порядка объявления влияет и на обычное уничтожение, и на обработку исключений.
Что происходит с памятью при исключении из конструктора объекта, созданного через new?
Конструктор завершается исключением, объект не создаётся, а выделенная под него память освобождается соответствующей функцией освобождения. При этом деструкторы уже сконструированных членов всё равно вызываются. Вручную вызывать delete для указателя, если new не завершился успешно, нельзя: управление указателем в таком случае обычно не возвращается вызывающему коду.