Sign in to follow this  
Daave

virtual destruction

Recommended Posts

This isn't something anyone would want to do (it's bad code), but I want to know why it doesn't work as expected anyway. I'm sure it's something subtle about the virtual function table with destructors.
struct VirtualTest1
{
	~VirtualTest1(){ cout<<"VirtualTest1 Dtor\n"; }
};

struct VirtualTest2 : public VirtualTest1
{
	virtual ~VirtualTest2(){ cout<<"VirtualTest2 Dtor\n"; }
};

struct VirtualTest3 : public VirtualTest2
{
	~VirtualTest3(){ cout<<"VirtualTest3 Dtor\n"; }
};

struct VirtualTest4 : public VirtualTest3
{
	~VirtualTest4(){ cout<<"VirtualTest4 Dtor\n"; }
};
//somewhere
VirtualTest1* pDerive1 = new VirtualTest4;
delete pDerive1;
It will execute VirtualTest1 destructor and then crash, why? Please be very specific, thanks.

Share this post


Link to post
Share on other sites
The destructor should be virtual in the base class. That way the correct destructor can be called. Calling the destructor for base class on an instance of a derived class is probably undefined behavior, so crashing is normal.

Share this post


Link to post
Share on other sites
I couldn't reproduce the crash on either MSVC or GCC, using a minimal program wrapped around your code.

Still, deleting a type through a pointer to a Base with a non-virtual destructor is bound to be undefined behaviour, so it could crash on your machine. Though I cannot see how most common C++ implementations would generate code that would crash with your example.

What compiler did you use, and did you change any interesting flags?

Share this post


Link to post
Share on other sites
Quote:
Original post by alvaro
The destructor should be virtual in the base class. That way the correct destructor can be called. Calling the destructor for base class on an instance of a derived class is probably undefined behavior, so crashing is normal.

The normal behavior it to simply run through the destructor you explicitly called (and it's parents destructors). For example if there was no virtual destructor in that chain and my pointer was a VirtualTest2 it would call VirtualTest2 dtor then VirtualTest1 dtor. This is why virtual destructors were invented - so you can call the dtor from anywhere in the polymorphic chain and destruct the entire chain.

Quote:
Original post by rip-off
I couldn't reproduce the crash on either MSVC or GCC, using a minimal program wrapped around your code.


Still, deleting a type through a pointer to a Base with a non-virtual destructor is bound to be undefined behaviour, so it could crash on your machine. Though I cannot see how most common C++ implementations would generate code that would crash with your example.

What compiler did you use, and did you change any interesting flags?[/quote]

VS2008 and 2003 on 2 different machines. The behavior I would expect is VirtualTest1 dtor being called, and that's it, however something is happening with the virtual method table to complicate the case.

Share this post


Link to post
Share on other sites

#include <iostream>

struct VirtualTest1
{
~Base(){std::cout << __FUNCTION__ << "\n";}
};

struct VirtualTest2 : public VirtualTest1
{
virtual ~Derived1(){std::cout << __FUNCTION__ "\n";}
};

struct VirtualTest3 : public VirtualTest2
{
~Derived2(){std::cout << __FUNCTION__ "\n";}
};

struct VirtualTest4 : public VirtualTest3
{
~Derived3(){std::cout << __FUNCTION__ "\n";}
};

int main()
{
VirtualTest1* ptr = new VirtualTest4;
delete ptr;
}


This will give an assert with Visual Studio 2008 Professional in Debug build but no error in Release build. All settings are default. But the answer for the problem has already been given. Its undefined behaviour. Add a virtual destructor in the class that should be derived from so that all destructors can be called.

Share this post


Link to post
Share on other sites
Quote:
Original post by Daave
VS2008 and 2003 on 2 different machines. The behavior I would expect is VirtualTest1 dtor being called, and that's it, however something is happening with the virtual method table to complicate the case.
Can we see the generated assembly? What is the exact crash you get?

Share this post


Link to post
Share on other sites
ya my bad, an assert, and I just realized you're right, it's ok in release

So why is it an undefined behavior? Shouldn't the behavior be the same as if there were no virtual destructor in the chain? (as long as you are calling a dtor more base than the first virtual dtor)

And before another person says it - im well aware the base class should be a virtual destructor, that wasn't the point of the post or question!

Share this post


Link to post
Share on other sites
Trying to understand why two similar programs that invoke undefined behavior have different behavior in practice is not a very interesting thing to do. Just don't invoke undefined behavior.

In your case, it probably has something to do with the presence of the virtual table. You can try to define a different virtual method in VirtualTest2, leave the destructors as non-virtual and see if you can still get the crash.

Anyway, you invoked undefined behavior and your program crashed. Or didn't. Yawn.

Share this post


Link to post
Share on other sites
Quote:
Original post by alvaro
Trying to understand why two similar programs that invoke undefined behavior have different behavior in practice is not a very interesting thing to do. Just don't invoke undefined behavior.

In your case, it probably has something to do with the presence of the virtual table. You can try to define a different virtual method in VirtualTest2, leave the destructors as non-virtual and see if you can still get the crash.

Anyway, you invoked undefined behavior and your program crashed. Or didn't. Yawn.


Go back a step, how did you determine this invoked an undefined behavior?

Share this post


Link to post
Share on other sites
Quote:
Original post by Daave
Go back a step, how did you determine this invoked an undefined behavior?


For instance, see here. It looks like 5.3.5/3 in the C++ standard says this is undefined behavior. I haven't bothered to check my copy, though.

Share this post


Link to post
Share on other sites
Quote:
Original post by Daave
The normal behavior it to simply run through the destructor you explicitly called (and it's parents destructors).

Funny, your code looks like C++. That's not normal behaviour for C++. Normal behavior in C++ is to use the static type of the object to determine the destructor to invoke. The static type of VirtualTest1* pDerive1 is pointer to VirtualTest1, so the only destructor to be invoked would be ~VirtualTest1().

If you want the dynamic type to be used (and apprently you do), you need to make ~VirtualTest1() virtual. That's what virtual destructors are for.

Share this post


Link to post
Share on other sites
Quote:
Original post by Bregma
Quote:
Original post by Daave
The normal behavior it to simply run through the destructor you explicitly called (and it's parents destructors).

Funny, your code looks like C++. That's not normal behaviour for C++. Normal behavior in C++ is to use the static type of the object to determine the destructor to invoke. The static type of VirtualTest1* pDerive1 is pointer to VirtualTest1, so the only destructor to be invoked would be ~VirtualTest1().

If you want the dynamic type to be used (and apprently you do), you need to make ~VirtualTest1() virtual. That's what virtual destructors are for.


You're confused about what I said, because we are in agreement - except I am confused why you're calling VirtualTest1* pDerive1 a static type. Anyway, in this case the destructor explicitly called was the VirtualTest1 destructor, and since it has no parents it calls nothing else. So as I said... "The normal behavior it to simply run through the destructor you explicitly called (and it's parents destructors)."

Edit: Oh and also, this question and thread has absolutely NOTHING to do with how to correctly code it - this code has no purpose or intention, it's a purely pedantic irrelevant to real life question. So as I said, please no more posts saying VirtualTest1 dtor should be virtual, not the point!

Share this post


Link to post
Share on other sites
I don't see how my previous post doesn't answer the question... Your compiler is being helpful by letting you know that you are invoking undefined behavior.

Share this post


Link to post
Share on other sites
Quote:
Original post by Daave
You're confused about what I said, because we are in agreement - except I am confused why you're calling VirtualTest1* pDerive1 a static type.
As far as the compiler is concerned, it's a static type - it's a pointer to a VirtualTest1 class, and the VirtualTest1 class has no virtual functions - the compiler has no reason to think it's a dynamic type.

Share this post


Link to post
Share on other sites
Quote:
Original post by alvaro
I don't see how my previous post doesn't answer the question... Your compiler is being helpful by letting you know that you are invoking undefined behavior.


It mostly did, thank you. Undefined doesn't mean random - and to me it doesn't mean stop thinking about what happened, even if it is compiler specific.

My post after that was just addressing the other dude

Quote:
Original post by Evil Steve
As far as the compiler is concerned, it's a static type - it's a pointer to a VirtualTest1 class, and the VirtualTest1 class has no virtual functions - the compiler has no reason to think it's a dynamic type.

Wasn't familiar with that terminology, thanks.

Share this post


Link to post
Share on other sites

Actually, that's the same terminology the standard uses. I found this link which has the text for that chapter of the standard.

Quote:
3 In the first alternative (delete object), if the static type of the
operand is different from its dynamic type, the static type shall be a
base class of the operand's dynamic type and the static type shall
have a virtual destructor or the behavior is undefined. In the second
alternative (delete array) if the dynamic type of the object to be
deleted differs from its static type, the behavior is undefined.19)


Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this