virtual destructor?

B

Bonj

Hello,
Can anyone help me with these fairly simple questions.

1) What is the point in virtual destructors - I've heard it's a good thing
for base-classes, but what are the advantages and disadvantages of doing so
/ or not as the case maybe?

2) In the following example, are code sections BD and DD *both* going to get
called? I assume they both always will, but is this guaranteed beyond all
odds in all cases? (When I say is it guaranteed - I mean will they both get
called *at some point*, not necessarily in any particular order)

class B
{
B() {}
virtual ~B() {// code BD //}
};
class D : public B
{
D() {}
virtual ~D() {//code DD//}
};
 
E

Evan

The answer 1 is best illustrated by example:

struct base
{
base() {}
~base() {}
};

class derived
{
int *p;
public:
derived() { p = new int[100]; }
~derived() { delete[] p; }
}

int main()
{
base *b = new derived();
// do some stuff with b
delete b; // oops!
}

Now when b is deleted (the line marked oops), ~base() is called but not
~derived(), so the space pointed to by p in the derived class isn't
deallocated, and you have a memory leak.

If you make the destructor virtual, ~derived() is called as normal.

The only downside I know of to doing this is the overhead of a virtual
function call in general; that is essentially, calling through a double
indirection pointer instead of directly, and the overhead of setting
things up to use virtual functions at all if the destructor is the only
virtual thing. (There is a fair amount of "base overhead" for having
the first virtual function, plus a small cost for each additional one.)
 
D

Donovan Rebbechi

Hello,
Can anyone help me with these fairly simple questions.

1) What is the point in virtual destructors - I've heard it's a good thing
for base-classes, but what are the advantages and disadvantages of doing so
/ or not as the case maybe?

Any class that you intend to use polymorphically as a base class (any class that
has virtual functions) should have a virtual destructor.
http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.7
2) In the following example, are code sections BD and DD *both* going to get
called?

In both of these examples, yes:

void f(){
D x;
// ~D(); ~B();
}

void g() {
B* x = new D;
delete x; // ~D(); ~B();
}
I assume they both always will, but is this guaranteed beyond all
odds in all cases?

In all reasonable examples, excluding obvious memory leaks.
(When I say is it guaranteed - I mean will they both get
called *at some point*, not necessarily in any particular order)

The derived class destructor always gets called first. It's important
that it happens in this order because the derived class destructor may
need to use resources held by the base class object.

Cheers,
 
B

Bonj

ok, thanks - see inline...

Evan said:
The answer 1 is best illustrated by example:

struct base
{
base() {}
~base() {}
};

class derived //(presuming you meant to put ' class derived : public
base' )
{
int *p;
public:
derived() { p = new int[100]; }
~derived() { delete[] p; }
}

int main()
{
base *b = new derived();
// do some stuff with b
delete b; // oops!
}

Now when b is deleted (the line marked oops), ~base() is called but not
~derived(), so the space pointed to by p in the derived class isn't
deallocated, and you have a memory leak.

ok, so I take it that to make sure *all* destructors of base classes all the
way
up the chain fire, I need to make them virtual (thus answering my second
question aswell...?)

But this raises another question I was pondering on, that I didn't ask:
Let's consider your code amended to have 'virtual ~base()' rather than
just '~base()'. When you do 'delete b', base's destructor fires aswell as
derived's,
because it's virtual. This is what we want. But my other question is - when
you do
'delete b', how does delete know that it's actually deleting an instance of
a 'derived',
rather than an instance of a 'base' - since 'b' is a pointer to a 'base'
(even though it is
actually a derived). What I'm hoping you're going to tell me is that when
'new' runs,
information about the size of the actual type of class instantiated
('derived') is stored
in some hidden memory, in order that delete can be intelligent and delete
the right amount
of memory no matter what kind of pointer it is. Can you just clarify this
for me?
If you make the destructor virtual, ~derived() is called as normal.

The only downside I know of to doing this is the overhead of a virtual
function call in general; that is essentially, calling through a double
indirection pointer instead of directly, and the overhead of setting
things up to use virtual functions at all if the destructor is the only
virtual thing. (There is a fair amount of "base overhead" for having
the first virtual function, plus a small cost for each additional one.)
I'm sure that if this is the worst performance hit this class library
suffers then it'll still be miles faster than mfc. ;-)
 
R

Ron Natalie

Evan said:
Now when b is deleted (the line marked oops), ~base() is called but not
~derived(), so the space pointed to by p in the derived class isn't
deallocated, and you have a memory leak.

Actually, deleting a derived object by the base class pointer when the
base class destructor is not virtual is UNDEFINED BEHAVIOR.

You can't make any assumptions about what is going to happen.
 
C

codigo

Evan said:
The answer 1 is best illustrated by example:

struct base
{
base() {}
~base() {}
};

class derived
{
int *p;
public:
derived() { p = new int[100]; }
~derived() { delete[] p; }
}

you meant:

class derived : public base
{
int *p;
public:
derived() : base()
{
p = new int[100];
}
~derived() // should be virtual as explained below
{
delete[] p;
}
};
 
E

Evan

ok, so I take it that to make sure *all* destructors of base classes
all the
way
up the chain fire, I need to make them virtual (thus answering my
second
question aswell...?)

Uh... I'm not sure if you have to explicitly mark as virtual the
destructor of a class that is both a subclass and superclass for it to
fire if an object of the most derived class is deleted through a
pointer to the base...
This is what we want. But my other question is - when you do
'delete b', how does delete know that it's actually deleting an instance of
a 'derived', rather than an instance of a 'base' - since 'b' is a pointer to a 'base'
(even though it is actually a derived). What I'm hoping you're going to tell me is that
when 'new' runs, information about the size of the actual type of class instantiated
('derived') is stored in some hidden memory, in order that delete can be intelligent
and delete the right amount of memory no matter what kind of pointer it is. Can you
just clarify this for me?

The short answer is 'yes'.

The long answer is that somehow new and delete have to work it out
amongst themselves how to do it. There are allocation schemes that
don't require the size to be stored explicitly, but rather computed
from, say, the address of the pointer. The implementation is up to the,
uh, implementor, but if you have a pointer allocated by new, delete has
to be able to figure out the right amount to deallocate.
 
R

Rayer

This question is simple

"Just remember if this class would be served as base class of other
class, then set deconstructor to virtual is ture"
 
B

Bonj

Uh... I'm not sure if you have to explicitly mark as virtual the
destructor of a class that is both a subclass and superclass for it to
fire if an object of the most derived class is deleted through a
pointer to the base...

What I meant was, if *all* the destructors are virtual, does that mean *all*
the destructors guaranteed to fire?
 
B

Bonj

Ron Natalie said:
Actually, deleting a derived object by the base class pointer when the
base class destructor is not virtual is UNDEFINED BEHAVIOR.

You can't make any assumptions about what is going to happen.


But it's ok when the destructor is virtual, yes?
 
C

Carl Daniel [VC++ MVP]

Bonj said:
What I meant was, if *all* the destructors are virtual, does that
mean *all* the destructors guaranteed to fire?

If the destructor of the class that is the static type that you're deleting
is virtual, then all destructors of the actual object are guaranteed to run
in reverse order of construction.

Example:

struct A
{
~A() {}
};

struct B : A
{
virtual ~B() {}
};

struct C : B
{
~C() {} // implicitly virtual
};

struct D : C
{
~D() {} // implicitly vritual
};


void f()
{
D* pd = new D();

C* pc = pd;
B* pb = pc;
A* pa = pb;

// Of course, in real code, you'd only do 1 of the following
// but for eeposition:

delete pd; // all destructors run
delete pc; // all destructors run
delete pb; // all destructors run
delete pa; // undefined behavior
}

In all the cases where destructors run, they're guranteed to be in the order
D, C, B, A.

Multiple inheritance and virtual inheritance follow the same pattern - as
long as the destructor of the declared type of the pointer you're deleting
is virtual, then all destructors will run in the reverse order of
construction.

You might also see http://www.gotw.ca/gotw/080.htm (and in fact, everything
on the GOTW site).

-cd
 

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

No members online now.

Forum statistics

Threads
473,763
Messages
2,569,562
Members
45,038
Latest member
OrderProperKetocapsules

Latest Threads

Top