exception with non virtual destructor c++
Asked Answered
T

4

14

When we go out of catch block scope, does the exception destructor get called? (In case we don't rethrow it)

Suppose I have class A, and that its destructor is not virtual. B inherits A. Suppose some function threw object of B class as an exception, and it was caught by a catch block

catch(A& a){
...
}

If the exception destructor should be called when go out of catch scope, in this case only the base class A's destructor will be called?

Cornstalks: live trial result in calling both class destructor.

It contradicts my logic. Explain someone?

Timmie answered 5/2, 2015 at 20:48 Comment(4)
I'm a bit interested in why you ask this question; it's a very valid, not really basic, question, but it indicates you care about the point in time when your exception's destructor is called, which is nothing you'd normally do.Trochal
Huh, I'm not sure about that last part.Merman
@MarcusMüller: What's wrong with wanting to gain knowledge about the tools that we use?Merman
@LightnessRacesinOrbit: Nothing! Nothing at all! It's an extremely good question. But it's really unusual to spend though on the lifetime of an exception object; of course, you don't want exceptions to clutter your memory :)Trochal
U
5

OK, someone already answered your first question. I'll focus on this one:

if the exception destructor should be called when go out of catch scope, in this case only the base class A's d'tor will be called?

The implementation will always destroy the exception object properly regardless of how it is caught. The implementation constructs the exception object, so it knows how to destroy it. This is not the same as when you call delete through a pointer, because in that case there is incomplete information about the complete type of the object at that point (it may have been newed somewhere else) unless a virtual destructor exists.

Were this not the case, catch (...) would never work at all.

Unpeopled answered 5/2, 2015 at 21:7 Comment(7)
(it may have been newed somewhere else) - can you explain, please?Timmie
And, just for completeness, deleting a pointer to a derived object through a pointer to the base type, when the base type does not have a virtual destructor, produces undefined behavior. It might run the base destructor, but it might do something completely different.Evetta
@Timmie Sorry, that was a bit of an awkward sentence, but the point is that at the point in the code where a pointer is deleted, there's no way to match it up with the place where the same object was newed. That's why you need the virtual destructor, otherwise there is no way for the code to "know" which destructor to invoke.Unpeopled
Your explanation isn't convincing. A catch may be far away from the throw site just as a delete may be far away from the new site. An example and/or standard quote is needed, really.Merman
@LightnessRacesinOrbit throw transfers execution to some trampoline which then jumps into the catch block. Upon exit from the catch block there is another trampoline that destroys the exception object (if there are no exception_ptr objects referring to it). These blocks are generated in pairs and can be matched statically. At least that's my understanding; I could be wrong about this.Unpeopled
@LightnessRacesinOrbit I looked in the standard, but couldn't find anything. I can only assume that means the object is destroyed in a well-defined manner.Unpeopled
@Brian: Usually if the standard doesn't define semantics then there isn't defined behaviour. In this case though I would tend to agree: this follows from other rules. I don't disagree with your conclusion but I do disagree with the rationale in your answer.Merman
M
5

when we go out of catch block scope, does the exception destructor is called? (In case we don't rethrow it)

Yes:

[C++11: 15.1/4]: [..] The exception object is destroyed after either the last remaining active handler for the exception exits by any means other than rethrowing, or the last object of type std::exception_ptr (18.8.5) that refers to the exception object is destroyed, whichever is later. [..]


if the exception destructor should be called when go out of catch scope, in this case only the base class A's d'tor will be called?

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()
Merman answered 5/2, 2015 at 20:51 Comment(4)
Actually, your sample indicates otherwise. I get the following output (using the same online compiler): ~B()~A()Diarrhoea
Ahem, Lightness, your coliru link shows both ~B() and ~A() printed... which is what Cornstalks' answer confirms... Just sayin'Learned
@InnocentBystander: I guess it's impossible to tell whether that ~B() is the temporary in the throw-expression, or whether that's being elided (which is legal) and we're seeing output from the catch.Merman
@InnocentBystander: ok fixed it - it's a real proof now.Merman
U
5

OK, someone already answered your first question. I'll focus on this one:

if the exception destructor should be called when go out of catch scope, in this case only the base class A's d'tor will be called?

The implementation will always destroy the exception object properly regardless of how it is caught. The implementation constructs the exception object, so it knows how to destroy it. This is not the same as when you call delete through a pointer, because in that case there is incomplete information about the complete type of the object at that point (it may have been newed somewhere else) unless a virtual destructor exists.

Were this not the case, catch (...) would never work at all.

Unpeopled answered 5/2, 2015 at 21:7 Comment(7)
(it may have been newed somewhere else) - can you explain, please?Timmie
And, just for completeness, deleting a pointer to a derived object through a pointer to the base type, when the base type does not have a virtual destructor, produces undefined behavior. It might run the base destructor, but it might do something completely different.Evetta
@Timmie Sorry, that was a bit of an awkward sentence, but the point is that at the point in the code where a pointer is deleted, there's no way to match it up with the place where the same object was newed. That's why you need the virtual destructor, otherwise there is no way for the code to "know" which destructor to invoke.Unpeopled
Your explanation isn't convincing. A catch may be far away from the throw site just as a delete may be far away from the new site. An example and/or standard quote is needed, really.Merman
@LightnessRacesinOrbit throw transfers execution to some trampoline which then jumps into the catch block. Upon exit from the catch block there is another trampoline that destroys the exception object (if there are no exception_ptr objects referring to it). These blocks are generated in pairs and can be matched statically. At least that's my understanding; I could be wrong about this.Unpeopled
@LightnessRacesinOrbit I looked in the standard, but couldn't find anything. I can only assume that means the object is destroyed in a well-defined manner.Unpeopled
@Brian: Usually if the standard doesn't define semantics then there isn't defined behaviour. In this case though I would tend to agree: this follows from other rules. I don't disagree with your conclusion but I do disagree with the rationale in your answer.Merman
U
3

While I'm not quoting from the standard, it seems that throwing a B and catching a A& will result in both A's and B's destructors getting called. 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;
}

Output:

beginning main scope
beginning inner scope
calling throwit()
caught exception
B::~B
A::~A
ending inner scope
ending main scope

As you can see, both destructors get called. The extra scope printing shows very clearly exactly when the destructors get called (at the end of the catch block).

Ulyanovsk answered 5/2, 2015 at 20:57 Comment(0)
E
3

Whenever the Standard says that an object is destroyed, it means that the correct most-derived destructor is invoked.

Always.

When you polymorphically delete an object without a virtual destructor, or you terminate (via delete operator or explicit destructor call) an object of incomplete type and the proper destructor is non-trivial, the Standard does not say that object is destroyed. It does not say that the base class destructor is invoked. It says you have undefined behavior.

Exarch answered 6/2, 2015 at 0:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.