Virtual Destructors And Upcasting

M

Marcelo De Brito

Hi!

I am a little bit confused on virtual destructos and upcasting through
pointers. The following code explains my doubt:

class b1 {
public:
~b1() {cout << "b1::~b1()" << endl;}
};

class d1 : public b1 {
public:
~d1 {cout << "d1::~d1()" << endl;}
};

class b2 {
public:
virtual ~b2() {cout << "b2::~b2()" << endl;}
};

class d2 : public b2 {
public:
~d2() {cout << "d2::~d2()" << endl;}
};

int main()
{
b1* b1p = new d1; //Upcast
b2* b2p = new d2; //Upcast
}

The output I got is:

b1::~b1()
d2::~d2()
b2::~b2()

Why did only the "b2" pointer "b2p" called the derived class "d2"
destructor?

I know the virtual statement has to do with it, but since an upcasting
is being made, I thought the base class pointer "b2p" would have
access only to the base class members and, thus, would be not able of
calling any member declared in the derived class ("d2"), including the
destructor "d2::~d2()".

I appreciate any comment, suggestion, and etc.

Thank You!

Marcelo
 
A

Alf P. Steinbach

* Marcelo De Brito:
I am a little bit confused on virtual destructos and upcasting through
pointers. The following code explains my doubt:

class b1 {
public:
~b1() {cout << "b1::~b1()" << endl;}
};

class d1 : public b1 {
public:
~d1 {cout << "d1::~d1()" << endl;}
};

class b2 {
public:
virtual ~b2() {cout << "b2::~b2()" << endl;}
};

class d2 : public b2 {
public:
~d2() {cout << "d2::~d2()" << endl;}
};

int main()
{
b1* b1p = new d1; //Upcast
b2* b2p = new d2; //Upcast
}

Well, one might call it an "implicit upcast", for lack of a better term.
Technically it's just a conversion, while a cast involves using cast notation.

The output I got is:

b1::~b1()
d2::~d2()
b2::~b2()

No you didn't get that output with that code.

Nowhere are your objects destroyed.

Why did only the "b2" pointer "b2p" called the derived class "d2"
destructor?

I know the virtual statement has to do with it, but since an upcasting
is being made, I thought the base class pointer "b2p" would have
access only to the base class members and, thus, would be not able of
calling any member declared in the derived class ("d2"), including the
destructor "d2::~d2()".

Read up on "virtual" in your textbook.

The effect is essentially to support calls to routine definitions in the dynamic
type of the object.


Cheers & hth.,

- Alf
 
M

Marcelo De Brito

Hi!

Thank you very much for your reply.

I forgot to put two lines of code that would give the aforementioned
output. Then, the code must be:

#include <iostream>
using namespace std;

class b1 {
public:
~b1() {cout << "b1::~b1()" << endl;}

};

class d1 : public b1 {
public:
~d1() {cout << "d1::~d1()" << endl;}

};

class b2 {
public:
virtual ~b2() {cout << "b2::~b2()" << endl;}

};

class d2 : public b2 {
public:
~d2() {cout << "d2::~d2()" << endl;}

};

int main()
{
b1* b1p = new d1; //Upcast
delete b1p;
b2* b2p = new d2; //Upcast
delete b2p;
}

Now, the output is what I said in the previous message:

b1::~b1()
d2::~d2()
b2::~b2()

I should reformulate the question above: Why, when deleting the "b1p"
pointer, only the base class destructor is called and when deleting
the "b2p" pointer both base class and derived class destructors are
called?
Read up on "virtual" in your textbook.

I have read it so many times and I could not get the right message
from the text. But I appreciate and thank your explanation.

Sorry for the minor errors in the code above. I wrote it in a hurry
before going to the bakery! :)

I appreciate any comment, suggestion, etc.

Thank You!

Marcelo
 
A

Alf P. Steinbach

* Marcelo De Brito:
Hi!

Thank you very much for your reply.

I forgot to put two lines of code that would give the aforementioned
output. Then, the code must be:

#include <iostream>
using namespace std;

class b1 {
public:
~b1() {cout << "b1::~b1()" << endl;}

};

class d1 : public b1 {
public:
~d1() {cout << "d1::~d1()" << endl;}

};

class b2 {
public:
virtual ~b2() {cout << "b2::~b2()" << endl;}

};

class d2 : public b2 {
public:
~d2() {cout << "d2::~d2()" << endl;}

};

int main()
{
b1* b1p = new d1; //Upcast
delete b1p;
b2* b2p = new d2; //Upcast
delete b2p;
}

Now, the output is what I said in the previous message:

b1::~b1()
d2::~d2()
b2::~b2()

I should reformulate the question above: Why, when deleting the "b1p"
pointer, only the base class destructor is called

Formally it's Undefined Behavior to do 'delete p' when the dynamic type of the
object pointed to is different from the static referent type of p.

So formally anything could happen.

What happens in practice here is that the compiler simply assumes that the code
doesn't have UB, and since in C++ it's impractical to add any runtime check it
doesn't do that either, and so the result is the same as if you just had the
base type subobject.

and when deleting
the "b2p" pointer both base class and derived class destructors are
called?


I have read it so many times and I could not get the right message
from the text. But I appreciate and thank your explanation.

Uh, what book is that?

Short explanation: when a method m is virtual in a class B it's virtual in all
derived classes, and then when o is of statically known type B or a derived
class, a call o.m() effectively calls the first m implementation found by a
search starting in o's bottom-level *dynamic type* (d2 above) and up the
inheritance chains.

In practice, in C++ there's no actual search, but instead the same effect as the
search would yield is achieved in constant time by having a hidden pointer to a
type specific table of methods, in each object.


Cheers & hth.,

- Alf
 
M

Marcelo De Brito

Hi!

I understand the concept of virtual, what I have not got yet is the
way the virtual concept deals with situations like that I described
above.

In my mind, a base class object has access only to its members, but in
the code above we can verify a base class pointer ("b2p") calling a
member function (the destructor "d2::~d2()") that is not included in
the base class. Let alone the upcasting that is being done, what would
cause only base class' members to be available for calling.

At least is that what I have understood so far, but if it holds any
mistakes, I would be grateful if you could correct them.

Does the base class' VTABLE contain any address to derived classes'
members? In my mind, a base class' VTABLE holds only addresses of its
own members. while the derived classes' VTABLEs may or may not hold
addresses to base class' members.

I appreciate any correction, suggestion, comments and so on.

Thank You!

Marcelo
 
A

Alf P. Steinbach

* Marcelo De Brito:
Hi!

I understand the concept of virtual, what I have not got yet is the
way the virtual concept deals with situations like that I described
above.

In my mind, a base class object has access only to its members, but in
the code above we can verify a base class pointer ("b2p") calling a
member function (the destructor "d2::~d2()") that is not included in
the base class. Let alone the upcasting that is being done, what would
cause only base class' members to be available for calling.

At least is that what I have understood so far, but if it holds any
mistakes, I would be grateful if you could correct them.

Does the base class' VTABLE contain any address to derived classes'
members? In my mind, a base class' VTABLE holds only addresses of its
own members. while the derived classes' VTABLEs may or may not hold
addresses to base class' members.

I appreciate any correction, suggestion, comments and so on.

Go to the URL in the signature below.

Check out the pointers tutorial.


Cheers & hth.,

- Alf
 
M

Marcelo De Brito

Hi, Alf!

Thank you very much for the PDF text on pointers. It will be a joy to
read it along this coming week.

Thank You!

Marcelo
 
J

James Kanze

I am a little bit confused on virtual destructos and upcasting
through pointers. The following code explains my doubt:
class b1 {
public:
~b1() {cout << "b1::~b1()" << endl;}
};
class d1 : public b1 {
public:
~d1 {cout << "d1::~d1()" << endl;}
};
class b2 {
public:
virtual ~b2() {cout << "b2::~b2()" << endl;}
};
class d2 : public b2 {
public:
~d2() {cout << "d2::~d2()" << endl;}
};
int main()
{
b1* b1p = new d1; //Upcast
b2* b2p = new d2; //Upcast
}
The output I got is:
b1::~b1()
d2::~d2()
b2::~b2()

That surprises me. I don't get any output when I run your
program. Did you forget a couple of delete's?
Why did only the "b2" pointer "b2p" called the derived class
"d2" destructor?

The official answer is that if you delete through a pointer to a
type B, if the actual object doesn't have type B and the
destructor of B isn't virtual, you have undefined behavior.
Anything can happen.

In simple cases, like the above, the most likely result is that
only the base class constructor will be called. In more
complicated cases, there could be a core dump, or the free space
arena might be trashed.
I know the virtual statement has to do with it, but since an
upcasting is being made, I thought the base class pointer
"b2p" would have access only to the base class members and,
thus, would be not able of calling any member declared in the
derived class ("d2"), including the destructor "d2::~d2()".

In general, virtual means that the call is resolved according to
the dynamic type, not the static type of the object. In the
case of destructors, the standard requires a virtual destructor
for the delete to work.
 
B

Bart van Ingen Schenau

Marcelo said:
Hi!

I understand the concept of virtual, what I have not got yet is the
way the virtual concept deals with situations like that I described
above.

In my mind, a base class object has access only to its members, but in
the code above we can verify a base class pointer ("b2p") calling a
member function (the destructor "d2::~d2()") that is not included in
the base class. Let alone the upcasting that is being done, what would
cause only base class' members to be available for calling.

At least is that what I have understood so far, but if it holds any
mistakes, I would be grateful if you could correct them.

It seems that you are too hung up on the issue that for overriding a
virtual function, the names have to be the same and that the names of
the destructors appear to be different.
Technically, destructors don't have a name, and the notation
ClassName::~ClassName was only invented because there had to be some way
to designate a destructor when declaring/defining them. As that might
not make understanding the issue of virtual destructors easier, you can
imagine that internally in the compiler all destructors have the same
name (for example $destructor$). This means that the source code
constructs 'b1::~b1()' and 'd1::~d1()' actually name the functions 'b1::
$destructor$()' and 'd1::$destructor$()'.
Does the base class' VTABLE contain any address to derived classes'
members? In my mind, a base class' VTABLE holds only addresses of its
own members. while the derived classes' VTABLEs may or may not hold
addresses to base class' members.

That is a correct mental view. A compiler is not required to use a
vtable, so don't try to directly access one, but it is a useful tool in
explaining things.

If we take these classes:

class B1 {
public:
virtual ~B1();
virtual void foo();
};

class D1 : public B1 {
virtual ~D1();
virtual void bar();
};

Then the vtables for these classes would look like this (in pseudo C++):
pointer_t B1_vtable[] = {&B1_RTTI, &B1::$destructor$, &B1::foo};
pointer_t D1_vtable[] = {&D1_RTTI, &D1::$destructor$, &B1::foo,
&D1::bar};

As you can see, the destructors for D1 and B1 occupy the same slot in
the vtables.
This is why, when you have
B1* pb1 = new D1;
delete pb1;
that the destructor for D1 gets called.
I appreciate any correction, suggestion, comments and so on.

Thank You!

Marcelo

Bart v Ingen Schenau
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top