Virtual Destructors logic in inheritance

Pages: 12
Feb 2, 2016 at 9:17pm
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 Feb 2, 2016 at 9:34pm
Feb 2, 2016 at 9:55pm
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 Feb 2, 2016 at 9:56pm
Feb 2, 2016 at 9:59pm
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
Feb 2, 2016 at 10:01pm
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.
Feb 2, 2016 at 10:31pm
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!
Feb 2, 2016 at 10:58pm
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.
Feb 2, 2016 at 11:11pm
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.
Feb 2, 2016 at 11:18pm
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?
Feb 2, 2016 at 11:21pm
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?






Feb 3, 2016 at 12:21am
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.
Feb 3, 2016 at 6:18am
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?
Feb 3, 2016 at 8:41am
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.
Feb 3, 2016 at 10:02am
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...
Feb 3, 2016 at 11:17am
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 Feb 3, 2016 at 11:19am
Feb 3, 2016 at 11:20am
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 Feb 3, 2016 at 11:22am
Feb 3, 2016 at 11:32am
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 ..."
Feb 3, 2016 at 12:59pm
@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;
}
Feb 3, 2016 at 2:06pm
I know. I was talking about the code in your second post.
Last edited on Feb 3, 2016 at 2:07pm
Feb 3, 2016 at 2:09pm
The version with non-vritual ~Base and non-virtual ~Derived is equally incorrect.
Feb 3, 2016 at 2:12pm
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