Деструкторите извикват ли се след хвърляне в C++?

Изпълних примерна програма и наистина се извикват деструктори за обекти, разпределени в стека, но това гарантирано ли е от стандарта?


person Luchian Grigore    schedule 29.11.2011    source източник
comment
Разбира се, че е така. RAII, който е един от най-важните идиоми в C++, зависи от това.   -  person Jon    schedule 29.11.2011
comment
Да, това е целият смисъл на обработката на изключения.   -  person Kerrek SB    schedule 29.11.2011
comment
@Jon & Kerrek SB, Ако изключението не бъде уловено, не е гарантирано да се случи размотаване на стека, то е дефинирано изпълнение: вижте отговора на NPE по-долу, последната част е цитатът от стандарта, който казва това.   -  person Philippe Carphin    schedule 17.09.2020


Отговори (3)


Да, гарантирано е (при условие, че изключението е уловено), до реда, в който се извикват деструкторите:

C++11 15.2 Конструктори и деструктори [except.ctor]

1 Тъй като управлението преминава от израз за хвърляне към манипулатор, деструкторите се извикват за всички автоматични обекти, конструирани след въвеждането на блока try. Автоматичните обекти се унищожават по реда, обратен на завършване на изграждането им.

Освен това, ако изключението бъде хвърлено по време на изграждането на обект, подобектите на частично изградения обект се гарантира, че ще бъдат правилно унищожени:

2 Обект с каквато и да е продължителност на съхранение, чиято инициализация или унищожаване е прекратено от изключение, ще има деструктори, изпълнени за всички негови напълно конструирани подобекти (с изключение на вариантните членове на клас, подобен на обединение), тоест за подобекти, за които основният конструктор (12.6.2) е завършил изпълнението и деструкторът все още не е започнал изпълнението. По същия начин, ако неделегиращият конструктор за обект е завършил изпълнението и делегиращ конструктор за този обект излезе с изключение, ще бъде извикан деструкторът на обекта. Ако обектът е бил разпределен в нов израз, съвпадащата функция за освобождаване (3.7.4.2, 5.3.4, 12.5), ако има такава, се извиква, за да освободи паметта, заета от обекта.

Целият този процес е известен като „размотаване на стека“:

3 Процесът на извикване на деструктори за автоматични обекти, конструирани по пътя от блок try до израз за хвърляне, се нарича „размотаване на стека“. Ако деструктор, извикан по време на размотаване на стека, излезе с изключение, се извиква std::terminate (15.5.1).

Развиването на стека формира основата на широко използваната техника, наречена Придобиването на ресурс е инициализация (RAII).

Обърнете внимание, че размотаването на стека не се извършва непременно, ако изключението не е уловено. В този случай зависи от реализацията дали ще се извърши размотаването на стека. Но независимо дали отвиването на стека е извършено или не, в този случай ви е гарантирано последно извикване на std::terminate.

C++11 15.5.1 Функцията std::terminate() [except.terminate]

2 В ситуация, в която не е намерен съвпадащ манипулатор, се дефинира от изпълнението дали стекът се развива или не, преди да се извика std::terminate().

person NPE    schedule 29.11.2011
comment
Забележка: относно строителството на обект, който е прекъснат. Самият обект не е унищожен (никога не е живял), това, което е гарантирано е, че подчастите (базови класове, атрибути), които са били напълно конструирани до момента, ще бъдат унищожени в обратен ред. - person Matthieu M.; 29.11.2011
comment
Добавена е информация за размотаване на стека (или не) за неуловено изключение. - person Cheers and hth. - Alf; 29.11.2011

Да, гарантирано е, че деструкторите ще бъдат извикани при размотаване на стека, включително размотаване поради хвърляне на изключение. Има само няколко трика с изключения, които трябва да запомните:

  • Деструкторът на класа не се извиква, ако в неговия конструктор е хвърлено изключение.
  • Изключението автоматично се хвърля отново, ако бъде уловено в блока за улавяне на списъка за инициализация на конструкцията.
person Community    schedule 29.11.2011
comment
3) Деструкторите не трябва никога да хвърлят изключения, тъй като няма няма начин да се справят с тях адекватно. - person DevSolar; 29.11.2011
comment
@DevSolar съществуват някои контрапримери. - person Cheers and hth. - Alf; 29.11.2011
comment
@AlfP.Steinbach: Всеки деструктор, хвърлен по време на разгъване на стека (поради хвърляне на друго изключение) ще terminate() вашия процес. Ще ми е интересно да видя контрапримери... - person DevSolar; 29.11.2011
comment
@DevSolar: вие (умишлено?) не сте наясно за какво бихте искали контрапримери. но по отношение на първото твърдение, че деструкторите никога не трябва да хвърлят изключения, не съвсем необичаен контрапример е обект, който представлява резултат от функция и който хвърля от своя деструктор, ако кодът на повикващия не е проверил дали представлява грешка. Друг пример е обект за защита на транзакции, който хвърля от своя деструктор, освен ако кодът, в който е вграден, не е успял в своите усилия (напр. да прехвърли собствеността върху нещо) и е извикал своя release метод. - person Cheers and hth. - Alf; 29.11.2011
comment
проблемът е, както забелязвате, че хвърлянето от деструктор може да бъде доста фатално, ако има необработено изключение (не размотаване на стека, тъй като можете да имате try, изпълнено в деструктор). Visual C++ никога не е прилагал функцията на стандарта за проверка и тази функция така или иначе не е адекватна. така че, това е малко проблематично, но не е тотално, тъй като кодът за използване може да бъде проектиран около него. - person Cheers and hth. - Alf; 29.11.2011
comment
@AlfP.Steinbach: Кодът за използване може да бъде проектиран около него. - Съгласен съм. Основното правило обаче е, че вашите обекти могат да бъдат използвани в контекст, който не сте предвидили, и не може да се грижи за проактивно. И ако поставите обекти, хвърлящи деструктор, в STL вектор, например, и този вектор бъде унищожен поради някакво несвързано разгъване на стека с изключение, той ще извика деструктора на вашия обект и приложението ви ще стане пуф. Разбира се, че мога да издърпам щифта от граната, да го боравя внимателно за известно време и да го поставя обратно. Но никога не трябва да правя това... - person DevSolar; 29.11.2011

Ако хвърлянето е уловено, тогава нормално cpp операциите продължават. Това включва деструктори и изскачане на стека. Ако обаче изключението не бъде уловено, изскачането на стека не е гарантирано.

Също така голо хвърляне или празно хвърляне не може да бъде уловено от моя мобилен компилатор.

пример:

#include <Jav/report.h>

int main()
{
 try { throw; }
 catch(...) { rep("I bet this is not caught"); }
 }
person Community    schedule 19.06.2019
comment
Дадох на това плюс едно. Не само, че не е уловен, но деструкторите за автоматични обекти в блока try (достатъчно лесен за поставяне на такъв) не се извикват (g++ 7.4.0/clang++ 6.0.0 ubuntu), -std=c++[11|14|17 ]. Изглежда не помага да се използва noexcept с основната декларация. Опитах и ​​подходящи опции за страницата на ръководството. Мога да получа предупреждение за гол хвърляне в блок за улавяне, но не и в блок за опит. Ако пропускам нещо, моля, осветете ме. - person davernator; 26.07.2019
comment
@davernator Благодаря за вашия плюс едно. Надявам се, че не сте пропуснали нещо, тъй като това е над моята заплата. Дори и сега. Спокойствие. - person ; 13.03.2020