2015-02-05 10 views
12

Kiedy wychodzimy z zakresu bloków catch, czy wywoływany jest destruktor wyjątku? (Jeśli go nie wyrzucimy)wyjątek z nie wirtualnym destruktorem C++

Załóżmy, że mam klasę A i że jej destruktor nie jest wirtualny. B dziedziczy A. Załóżmy niektórych funkcji rzucił obiekt klasy B jako wyjątek, i został złapany przez blok catch

catch(A& a){ 
... 
} 

Jeśli destruktor wyjątek powinien być nazywany gdy wychodzą z zakresu połowów, w w takim przypadku zostanie wywołany tylko destruktor klasy podstawowej A?

Cornstalks: Wyniki próbne na żywo wywołujące zarówno destruktor klasy.

Jest to sprzeczne z moją logiką. Wyjaśnić kogoś?

+1

Jestem trochę zainteresowany tym, dlaczego zadajesz to pytanie; to bardzo ważne, niezbyt podstawowe pytanie, ale wskazuje, że zależy ci na momencie, w którym wywoływany jest destruktor Twojego wyjątku, który normalnie nie jest normalny. –

+0

Huh, nie jestem pewien co do tej ostatniej części. –

+0

@ MarcusMüller: Co jest złego w chęci zdobycia wiedzy na temat narzędzi, z których korzystamy? –

Odpowiedz

5

OK, ktoś już odpowiedział na twoje pierwsze pytanie. Skoncentruję się na tym:

jeśli wyjątek destruktor powinien zostać wywołany, gdy wykracza poza zakres połowu, w tym przypadku tylko wywołanie klasy bazowej A?

Implementacja zawsze zniszczy obiekt wyjątku prawidłowo, niezależnie od tego, jak zostanie przechwycony. Implementacja konstruuje obiekt wyjątku, więc wie, jak go zniszczyć. To nie jest to samo co po wywołaniu delete przez wskaźnik, ponieważ w tym przypadku jest niepełna informacja o pełnym typie obiektu w tym miejscu (może to być new wydane gdzie indziej), chyba że istnieje wirtualny destruktor.

Gdyby tak nie było, catch (...) nigdy nie działałby wcale.

+0

(może to być nowość gdzie indziej) - czy możesz wyjaśnić, proszę? –

+3

I tylko dla kompletności, usunięcie wskaźnika do obiektu pochodnego przez wskaźnik do typu podstawowego, gdy typ podstawowy nie ma wirtualnego destruktora, generuje ** niezdefiniowane zachowanie **. Może uruchomić destruktor bazowy, ale może zrobić coś zupełnie innego. –

+0

@Day_Dreamer Przykro nam, to było trochę niezręczne zdanie, ale chodzi o to, że w punkcie kodu, w którym wskaźnik jest "delete'd, nie ma sposobu, aby dopasować go do miejsca, w którym ten sam obiekt był" nowy'ed. Dlatego potrzebny jest wirtualny destruktor, w przeciwnym razie kod nie będzie "wiedział", który destruktor wywoła. – Brian

5

kiedy wychodzimy z zakresu bloków catch, czy wywoływany jest destruktor wyjątku? (W przypadku, gdy nie przekaż ją)

Tak:

[C++11: 15.1/4]:[..] Przedmiotem wyjątek jest niszczony po obu ostatnich pozostałego aktywnego uchwytu do wyjścia wyjątku dowolnymi środkami inne niż ponowne rzucanie lub ostatni obiekt typu std::exception_ptr (18.8.5), który odnosi się do obiektu wyjątku, jest niszczony, w zależności od tego, co nastąpi później. [..]


jeśli destruktor wyjątek powinien być nazywany gdy wychodzą z zakresu połowów, w tym przypadku tylko klasa podstawę za d'tor będzie nazwany?

No:

#include <iostream> 

struct A 
{ 
    A() { std::cout << "A()"; } 
    A(const A&) { std::cout << "A(const A&)"; } 
    A(A&&) { std::cout << "A(A&&)"; } 
    ~A() { std::cout << "~A()"; } 
}; 

struct B : A 
{ 
    B() { std::cout << "B()"; } 
    B(const B&) { std::cout << "B(const B&)"; } 
    B(B&&) { std::cout << "B(B&&)"; } 
    ~B() { std::cout << "~B()"; } 
}; 

int main() 
{ 
    try { 
     throw B(); 
    } 
    catch (A&) { 
    } 
} 

// Output: A()B()~B()~A() 
+0

W rzeczywistości twoja próbka wskazuje inaczej. Otrzymuję następujące dane wyjściowe (przy użyciu tego samego kompilatora online): ~ B() ~ A() – zdan

+1

Ahem, Lightness, twoje łącze coliru pokazuje zarówno '~ B()' jak i '~ A()' wydrukowane ... odpowiedź Cornstalks potwierdza ... Po prostu mówię: –

+0

@InnocentBystander: Myślę, że nie da się powiedzieć, czy '~ B()' jest tymczasowe w _throw-expression_, czy to jest elided (co jest legalne) i my "widzę dane wyjściowe z' catch'. –

3

Chociaż ja nie cytuję od normy, wydaje się, że rzucanie B i łapania A& spowoduje obu A 's oraz B' s destruktory nazywane coraz. Live demo:

#include <iostream> 

struct A 
{ 
    ~A() { std::cout << "A::~A" << std::endl; } 
}; 

struct B : public A 
{ 
    ~B() { std::cout << "B::~B" << std::endl; } 
}; 

void throwit() 
{ 
    throw B{}; 
} 

int main() 
{ 
    std::cout << "beginning main scope" << std::endl; 

    { 
     std::cout << "beginning inner scope" << std::endl; 

     try 
     { 
      std::cout << "calling throwit()" << std::endl; 
      throwit(); 
     } 
     catch (A& a) 
     { 
      std::cout << "caught exception" << std::endl; 
     } 

     std::cout << "ending inner scope" << std::endl; 
    } 

    std::cout << "ending main scope" << std::endl; 
} 

wyjściowa:

zaczynają główny zakres
zaczynają zakres wewnętrzny
nazywając throwit()
złapać wyjątek
B :: ~ B
A :: ~ A
zakończenie zakresu wewnętrznego
zakończenie zakresu głównego

Jak widać, oba destruktory zostają wywołane. Dodatkowe drukowanie zakresu pokazuje bardzo dokładnie, kiedy wywoływane są destruktory (na końcu bloku catch).

3

Ilekroć norma mówi, że obiekt jest niszczony, oznacza to, że wywoływany jest poprawny najbardziej wyprowadzony destruktor.

Zawsze.

Po wielokształtnym usunięciu obiektu bez wirtualnego destruktora lub przerwaniu (przez operatora delete lub jawnego wywołania destruktora) obiektu niekompletnego typu i właściwego destruktora nie jest trywialny, Standard nie mówi, że obiekt jest zniszczony . Nie mówi, że wywoływany jest destruktor klasy podstawowej. Mówi, że masz niezdefiniowane zachowanie.

Powiązane problemy