virtual destruction

Started by
14 comments, last by alvaro 14 years, 1 month ago
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.
Advertisement
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.

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: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?

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.
#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.
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?
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!
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.

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?
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.

This topic is closed to new replies.

Advertisement