Virtual Destructors logic in inheritance

Pages: 12
Hello.

This is my draft:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Base
{
public:
	Base() {}
	virtual ~Base() { cout << "Base destructor called" << endl; }
};

class Derived : public Base
{
public:
	Derived() {}
	~Derived() { cout << "Derived destructor called" << endl; }
};

int main()
{	
	Base* base = new Base();
	Derived* derived = new Derived();
	Base* in = new Derived();
	
	// Calls ~Base()
	delete base;

	cout << endl << endl;

	// Calls ~Derived() and then ~Base()
	delete derived;
	
	cout << endl << endl;

	// If virtual ~Base(), calls ~Derived() and ~Base()
	// If ~Base(), ~Base()
	delete in;

	return 0;
}


Take a closer look at the comments in the main(). Those are my notes about the behavior of the line that comes after.

I need to go into detail with the 3rd case:

// If virtual ~Base(), calls ~Derived() and ~Base()
// If ~Base(), ~Base()

Why does this happen? What's the logic behind this?

Also, why If if set non-virtual ~Base() and virtual ~Derived() the program crashes?

Will you clean my ideas?

Thanks!
Last edited on
In C++, if pointer to base is passed to the delete expression, and the destructor of base is not virtual, the behavior is undefined. Anything at all can happen. The compiler isn't required to justify itself anymore than when you step outside of array bounds.
Last edited on
OP wrote:
Why does this happen? What's the logic behind this?

A base pointer to a derived object is still an instance of that derived object. You are just telling the compiler that you want to treat it as a certain way. Therefore, the derived object still gets created and so it has to be destroyed.


OP wrote:
Also, why If if set non-virtual ~Base() and virtual ~Derived() the program crashes?

Because it's undefined behavior: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#discussion-make-base-class-destructors-public-and-virtual-or-protected-and-nonvirtual
C++ supports different paradigms. Traditional (dynamic) inheritance is activated when a class has a virtual function. Such classes get a vtable. It's a table of pointers to functions. Each call to a virtual function goes is redirected thru this table.

In your case, the vtable for Derived (and Base) have one entry, the entry for the destructor.

You really should have written:
1
2
3
4
5
int main()
{	
	Base* base = new Base();
	Base* derived = new Derived();
	Base* in = new Derived();

The system will know that derived holds a Derived, even though it's declared to be of type Base.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base
{
public:
	Base() {}
	~Base() { cout << "Base destructor called" << endl; }
};

class Derived : public Base
{
public:
	Derived() {}
	virtual ~Derived() { cout << "Derived destructor called" << endl; }
};

int main()
{	
	Base* in = new Derived();

	delete in;

	_getch();
	return 0;
}


In this case the program crashes, if I delete the virtual keyword from the derived destructor, it won't crash anymore.

What happens in terms of CPU? What is it trying to do that can't do?

Thanks!
What happens in terms of CPU?

You don't need to think in terms of "CPU", but in terms of what is required by the standard. Your code evokes undefined behavior, so it is not valid (or is "ill-formed" in standardese.)

Garbage in, garbage out.
OP wrote:
What happens in terms of CPU? What is it trying to do that can't do?

Read your error code. My guess is that you are getting an invalid address exception or something similar. But the whole problem with undefined behavior is that we don't know what the system running the code is going to do so it's impossible for us to say.
In this case the program crashes, if I delete the virtual keyword from the derived destructor, it won't crash anymore.

You've got a memory overwrite that's hit the vtable. Are you sure there isn't more to your program than you're showing?
Guys i'm just trying to understand the REASON of this

How it works?

IF I set the Base's destr as non-virtual and the Derived classes destr as virtual... why doesnt the computer just call the Derived virtual destructor and only then calls the Base non-virtual destr?

Whats its problem?






Whats its problem?

The user.

delete takes a pointer. It doesn't know what type of object is pointed to, and if the object it points to doesn't have a virtual destructor, it has no way to know that there is another destructor that needs to be invoked.
The object it points to is a Derived, which HAS a virtual destructor.
It's the Base that has NOT

We said that if the Base destructor AND the Derived destructor are non-virtual then DELETE will only call the Base destructor (without calling Derived's one)

In my case, the Base destructor is STILL non-virtual.
But this time I set the Derived destructor as virtual and this made the difference (crash)

Why?
If ~Base() isn't virtual, ~Derived() will not be called.

I don't quite understand why so many people have answered this and you're still asking the same question. Perhaps you should all read the answers that have been provided.
The answers don't satisfy my question
I'm expecting WHY the crash occurs, WHY if i set a non virtual base destructor and a virtual Derived destructor the crash happens...
Because that's the way the people who wrote the implementation you're using, chose to write it.

Undefined behaviour is undefined. Your program could crash. It could work fine. It could do something else. Anything is possible, because the standard says this undefined behaviour.

Often, compiler writers implement compilers in such a way as to optimize code that is legal, defined behaviour. Whatever happens when undefined behaviour occurs, usually happens as a consequence of the way they've done that optimized implementation.

We can't tell you why the code generated by your compiler crashes, because we're not the ones who wrote your compiler.
Last edited on
I don't understand why it would crash. When I test it on my own computer it doesn't crash. Do you use Visual C++ in debug mode by any chance? In that case maybe it's just some debugging code that catch that something is wrong and causes the program to crash? Do you get an error message, or error code? To really know what is going on you might have to study the assembly output.
Last edited on
It shouldn't crash, but the OP is being difficult. I've asked a number of questions and said quite a bit, but he/she doesn't seem to care and definitly doesn't answer my question, and just repeats the question "I don't understand ..., why is this happening ..."
@Peter87 keep in mind that the code I posted above, in the first post, is correct and won't cause any crash

THIS is the code that crashes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Base
{
public:
	~Base() { cout << "Base destructor called" << endl; }
};

class Derived : public Base
{
public:
	virtual ~Derived() { cout << "Derived destructor called" << endl; }
};

int main()
{	
	Base* in = new Derived();

	delete in;

	_getch();
	return 0;
}
I know. I was talking about the code in your second post.
Last edited on
The version with non-vritual ~Base and non-virtual ~Derived is equally incorrect.
cire wrote:
delete takes a pointer. It doesn't know what type of object is pointed to,
gedamial wrote:
The object it points to is a Derived, which HAS a virtual destructor.
It's the Base that has NOT


Did you see where I said delete doesn't know what type of object is pointed to? It doesn't matter what (possibly derived) type it actually points to, all delete knows is you've given it a pointer-to-Base, and Base doesn't have a virtual destructor, so there is no reason for the compiler to invoke any destructor other than Base::~Base resulting in undefined behavior.

If you want to know what your compiler does specifically in this case where it is free to do absolutely anything at all, generate an assembly listing and inspect it. Of course that won't buy you much because behavior will probably change depending on the flags/settings you compile with or may even change with the addition of a random line of code.
Pages: 12