[comp.lang.c++] virtual destructors

west@calgary.UUCP (Darrin West) (12/22/87)

A colleague of mine, Greg Lomow, walked into my office this
morning and asked me an inspirational question.  "If class b is
derived from class a, and I create a new b, assigning it to
a pointer of type a*, what happens when I delete the pointer?  Will
the space for the extension due to b be deleted?"

I told him that it would call the destructor ~a().  malloc() or free()
whatever will make sure the entire block of memory gets freed up.
If you need to do something special to that part of the object associated
with class b, you need to set up a virtual function that ~a() can call.

This last statement (happily) is not true.  YOU CAN DECLARE THE DESTRUCTOR
FOR A TO BE VIRTUAL.  I am very surprised that this is not stressed in the
literature.  I don't know how many times I wrote a special virtual function
to delete elements from a generic (read: plain ole') linked list.  If
you do the following, then the derived destructor is called AUTOMATICALLY!

struct q_el{
    q_el *next;
    virtual ~q_el();
};

struct queue{
    q_el *first;
    ~queue();
};

queue::~queue()
{
    q_el *t = first;
    
    while(t!=NULL) delete t;
}

struct thing : public q_el{
    lump *l;
    thing(){l = new lump;};
    ~thing(){ delete l;};
};

And then make a nice queue of "things".
When you delete the queue, all the "things" AND their "lumps" go away!
I used to have to derive "queue" to a "thing_queue" and rewrite the
destructor to specifically delete "things".

Maybe everyone but me (I?) knows about this, but it was so neat I had
to let every else know (if they didn't already).

Things that work this good without being documented scare me.
Is this a bug or a feature?



-- 
Darrin West, Master's Unit (read: student).	..![ubc-vision,ihnp4]!
Department of Computer Science			alberta!calgary!west
University of Calgary.				Can you say '88 Winter Games?
Brain fault (cortex dumped)

bs@alice.UUCP (12/23/87)

Yes virtual destructors are very nice.
Yes they are part of the language (always were).

Sorry for forgetting to mention them explicitly.

I do mention them in my paper
	``The Evolution of C++: 1985-1987''
for the USENIX C++ ``workshop'' in Santa Fe.
The ``proceedings from that will be interesting reading.

lomow@calgary.UUCP (Greg Lomow) (01/20/88)

/*
A friend of mine (Darrin West) posted a message asking about the use
  of virtual destructors. The answer was that the use of virtual
  destructors is legal. Having used them, I can atest to the fact
  that they are very useful.

However, if you call delete AA when AA is NULL and the type of AA is
  "pointer to class a" where a's destructor is declared as virtual, a
  segmentation fault occurs. The C++ manual says that delete 
  can be applied to any pointer, NULL or non-NULL.

The example program shown below demonstrates this problem. If both
  pieces of code that are commented out are included in the program,
  a segmentation fault occurs.

We conjecture that delete is trying to access the destructor for AA
  like C++ would access any other virtual routine; i.e., by accessing a
  table with pointers to virtual functions, however if AA is NULL then 
  no such table exists.

Is this a bug or are we using virtual destructors wrong?

We are using version 1.2 of C++ on a Vax 11/780 running Unix 4.3.
*/
/*
error if virtual dtor and delete AA
ok    if non-virtual dtor and delete AA
ok    if virtual dtor and delete AA commented out
*/
#include <stream.h>
#define NULL	0

class a {
public:
  a();
  /* virtual*/ ~a();
};

a::a(){}
a::~a(){ cout << "deleted\n";}

main()
{ a *A = new a();
  delete A;

  a *AA = NULL;
//  delete AA;
}
-- 
Greg Lomow
	lomow@cpsc.calgary.cdn
      or
	....![ubc-vision,ihnp4]!alberta!calgary!lomow

pj@hrc63.co.uk (Mr P Johnson "Baddow") (02/28/89)

   Stroustrup does not seem to deal with the question of destructing derived
classes when all the calling function has is a pointer to the base class.  By
default only the base class destructor gets called.

In Oregon C++ (for Suns) v 1.1, it is possible to declare the base destructor
as virtual.  This then ensures that the appropriate destructor is called for
any derived classes provided that the _derived_class_has_a_destructor_.  If
the derived class has no explicit destructor then any member destructors will
not be called (rather than being called implicitly as they would be normally).

Does anyone know how other compilers behave?

e.g.

class Base {
    int foo;
public:
    Base( );
    virtual ~Base( ); // This is legal.
};


class Derived_1: Base {
    Problem mung; // Destructor should be called implicitly
public:
    Derived_1( ); // Constructor but no destructor
};


class Derived_2: Base {
    Problem mung; // Destructor should be called implicitly
public:
    Derived_2( ); // Constructor
    ~Derived_2( ); // and Destructor
};

main( ) {

    Base *p1, *p2;

    p1 = new Derived_1;
    p2 = new Derived_2; // p1 & p2 both point to base classes of Derived_1/2.

    delete p1; // Will not call Problem::~Problem.
    delete p2; // Will call Problem::~Problem.
};


//  Paul Johnson.

mball@cod.NOSC.MIL (Michael S. Ball) (03/01/89)

In article <551@hrc63.co.uk> pj@hrc63.co.uk (Mr P Johnson "Baddow") writes:
>In Oregon C++ (for Suns) v 1.1, it is possible to declare the base destructor
>as virtual.  This then ensures that the appropriate destructor is called for
>any derived classes provided that the _derived_class_has_a_destructor_.  If
>the derived class has no explicit destructor then any member destructors will
>not be called (rather than being called implicitly as they would be normally).

I believe this is a bug.  I will check on it and see that it gets handled
if it is.  This is the first time it has been reported, and it's not a
well-defined area of the language.  On logical grounds, though, it seems that
if the base class destructor is virtual the one generated for the derived
class should be as well.

Thanks for pointing it out.  If it turns out that the derived class shouldn't
be virtual, I'll make another posting on the subject.

Mike Ball
TauMetric Corporation
1094 Cudahy Place., Ste 302
San Diego, CA 92110
mball@cod.nosc.mil

rfg@riunite.ACA.MCC.COM (Ron Guilmette) (03/02/89)

In article <1430@cod.NOSC.MIL> mball@cod.nosc.mil.UUCP (Michael S. Ball) writes:
>In article <551@hrc63.co.uk> pj@hrc63.co.uk (Mr P Johnson "Baddow") writes:
>>In Oregon C++ (for Suns) v 1.1, it is possible to declare the base destructor
>>as virtual.  This then ensures that the appropriate destructor is called for
>>any derived classes provided that the _derived_class_has_a_destructor_.  If
>>the derived class has no explicit destructor then any member destructors will
>>not be called (rather than being called implicitly as they would be normally).
>
>I believe this is a bug.

I'm not sure what the "correct" semantics are myself, but I don't think it
is a bug.

I say that only because I have just recently been looking into the handling
of "virtual" in GNU G++ (and in particular the handling of "virtual" for
destructors) and I can tell you that G++ seems to do pretty much the same
thing as described above.  That is to say, if you have a base class which
has an explicitly virtual destructor, then the destructor will get a entry
in the vtable for that class.  Also, any classes derived from such a class
also (apparently) have entries for *their* destructors put into *their*
vtables (in the same slot number).  This seems to be true regardless of
whether or not the destructors in the derived classes explicitly have the
keyword "virtual" in their declarations.  Thus, it seems that these
destructors (in the derived classes) in effect become "implicitly"
virtual.

I also assume (but I have not yet checked) that (in G++) the destruction
of an object which has either an explicitly or implicitly virtual destructor
will be forced to go "indirect" through the vtable for that object.  If that
is true, then that means that the "appropriate destructor" should get called
for all such objects regardless of whether or not the compiler can "statically"
determine the actual class of an object which is being destroyed.

// Ron Guilmette  -  MCC  -  Experimental (parallel) Systems Kit Project
// 3500 West Balcones Center Drive,  Austin, TX  78759  -  (512)338-3740
// ARPA: rfg@mcc.com
// UUCP: {rutgers,uunet,gatech,ames,pyramid}!cs.utexas.edu!pp!rfg
-- 
// Ron Guilmette  -  MCC  -  Experimental (parallel) Systems Kit Project
// 3500 West Balcones Center Drive,  Austin, TX  78759  -  (512)338-3740
// ARPA: rfg@mcc.com
// UUCP: {rutgers,uunet,gatech,ames,pyramid}!cs.utexas.edu!pp!rfg

mball@cod.NOSC.MIL (Michael S. Ball) (03/03/89)

In article <101@riunite.ACA.MCC.COM> rfg@riunite.UUCP (Ron Guilmette) writes:
>In article <1430@cod.NOSC.MIL> mball@cod.nosc.mil.UUCP (Michael S. Ball) writes:
>>In article <551@hrc63.co.uk> pj@hrc63.co.uk (Mr P Johnson "Baddow") writes:
>>>In Oregon C++ (for Suns) v 1.1, it is possible to declare the base destructor
>>>as virtual.  This then ensures that the appropriate destructor is called for
>>>any derived classes provided that the _derived_class_has_a_destructor_.  If
>>>the derived class has no explicit destructor then any member destructors will
>>>not be called (rather than being called implicitly as they would be normally).
>>
>>I believe this is a bug.
>
>I'm not sure what the "correct" semantics are myself, but I don't think it
>is a bug.

It is not a question of whether virtual destructors are allowed.  They are,
and should behave like all other virtual functions, which is to say that
references to them will normally go through the virtual table.  The problem,
which is what I said was a bug, is that the destructors generated by the
compiler were NOT being treated as virtual even though the base class
destructor WAS virtual.  Since a user destructor for the derived class will be
virtual, it seems only logical that the compiler-generated one will be as
well.  It is not being so treated in the current version Oregon C++.  I
have no idea what G++ does in that situation, and the data you included
does not cover this case.

Mike Ball
TauMetric Corporation
1094 Cudahy Pl. Ste 302
San Diego, CA 92110
(619)275-6381
mball@cod.nosc.mil

ejbjr@ihlpm.ATT.COM (Branagan) (03/04/89)

> I'm not sure what the "correct" semantics are myself, but I don't think it
> is a bug.
> 
> I say that only because I have just recently been looking into the handling
> of "virtual" in GNU G++ (and in particular the handling of "virtual" for
> destructors) and I can tell you that G++ seems to do pretty much the same
> thing as described above.  That is to say, if you have a base class which
> has an explicitly virtual destructor, then the destructor will get a entry
> in the vtable for that class.  Also, any classes derived from such a class
> also (apparently) have entries for *their* destructors put into *their*
> vtables (in the same slot number).  This seems to be true regardless of
> whether or not the destructors in the derived classes explicitly have the
> keyword "virtual" in their declarations.  Thus, it seems that these
> destructors (in the derived classes) in effect become "implicitly"
> virtual.

If a function (any function, including a destructor) is declared
virtual in a base class, it will be virtual in all classes derived
from that base class.  I have not been able to find an explicit
statement of this rule in Stroustrups's book (though it might be
there somewhere), but some the examples make this behavior clear -
for instance, check out the example in section 7.2.8 on virtual
functions.  Function `print' in class  `employee' is declared as
virtual; in class `manager', derived from `employee', `print' is not
explicitly declared virtual.  It certainly would not make sense
for a non-virtual function to hide virtual functions - things just
would not work right.  The only other alternative would seem to be to
generate an error message if a virtual function in a derived class
was not explicitly declared virtual - something which is now out
of the question for compatability reasons.

I think we could use some better documentation about virtual
destructors though - its not obvious from the documentation that a
destructor can be declared virtual - BUT when should a destructor
not be virtual?  Can anyone give a reasonable example when the 
destructor for a derived class should not be called when an object
from a base class is to be destroyed?  Perhaps either all destructors
should be automatically virtual (which has minor performance
implications for classes with no derived classes), or the documentation
should strongly advise making destructors virtual (or at least discuss
virtual destructors and when/why they should be used).
-- 
Jim Branagan
(312) 416-7408 (work)
(312) 810-0969 (home)
Remember - Good planets are hard to find - Be kind to Mom.

ark@alice.UUCP (Andrew Koenig) (03/04/89)

In article <3130@ihlpm.ATT.COM>, ejbjr@ihlpm.ATT.COM (Branagan) writes:

> I think we could use some better documentation about virtual
> destructors though - its not obvious from the documentation that a
> destructor can be declared virtual - BUT when should a destructor
> not be virtual?  Can anyone give a reasonable example when the 
> destructor for a derived class should not be called when an object
> from a base class is to be destroyed?

If you ever say `delete' to a pointer to a base class that
actually points to an object of a derived class, the base
class must have a virtual destructor.

This rule is actually a slight oversimplification, but it's
probably the right one to follow rather than trying to figure
out the obscure (and possibly nonportable) exceptions.

Why not make all destructors virtual?  Performance.  If there
are no other virtual functions in the base class, making the
destructor virtual adds a word of overhead per object.  This
can be substantial if you're defining little tiny objects.
Moreover, C++ tries to impose overhead in only the cases that
need it.

In principle, once you've declared a virtual destructor in a
base class, you do not need to redeclare it in any derived
classes unless you need to say something there.  However,
some versions of C++ have a bug that causes incorrect code to
be generated if there's a virtual destructor in the base class
and no destructor at all in the derived class.  Thus if you
have a virtual destructor in a base class, define a destructor
explicitly in all defined classes even if the destructor is empty.
You don't have to say it's virtual in the derived classes.
-- 
				--Andrew Koenig
				  ark@europa.att.com

richard@pantor.UUCP (Richard Sargent) (01/20/90)

We have a problem which seems to require virtual destructors.
Is such a thing valid in C++? If not, is there an alternative?

Stroustrup's book doesn't get into this very deeply, so I hope
some one out there can advise us. The general description of
our problem follows. 

Thanks for any and all help.


We have a base class called FORM and a class derived from it
called GROUP. The derived class provides a group of FORM's.
It is supposed to support nested GROUP's. That is where the
problem arises. If I delete an item from a GROUP which is
a FORM, everything works as desired. But if the object to
be deleted is itself a GROUP, we would like to delete it
as a GROUP not as a FORM. Remember the GROUP class is a list
of the base class (FORM). 

If virtual destructors are valid, then we should be able to 
have a simple and elegant solution. If they are not, then I 
need to know how else I can solve this problem. 

The overall structure of the solution described above has some 
extremely desirable properties for us, so we would like to retain 
it as much as possible. We have come up with one workaround, a 
virtual function "deleteyourself()", but that is not nearly as 
nice as having things handled correctly and automatically from 
the destructors. 

A completely transposed solution (along the lines of the Smalltalk 
container classes) is acceptable, but not nearly as nice.


example (any syntax errors are the result of transcribing the text):

#include <string.h>
#include "gslist.h"      // generic singly linked list, Stroustrup 7.3.2

typedef FORM* PFORM;
...

struct FORM {
   char    *id;
   FORM(char *name) { id = new char[strlen(name)+1]; strcpy(id, name); }
      // minor question: how well defined is the interaction between
      // the strdup() function and the above "new char" plus strcpy()?
   ...
   ~FORM() { if (id) delete id; }
};

struct GROUP : FORM {
   gslist(PFORM)     members;          // group's member list

   GROUP(char *s) : (s) {}
   FORM  *addmember(FORM  *f) { members.append(f); return f; }
   GROUP *addmember(GROUP *g) { addmember((FORM *)g); return g; }
   ...
   ~GROUP() { PFORM fp; while (fp = members.get()) delete fp; }
}

...

main()
{
//
// Create tree
//
// root
//   a1
//   b1
//      b1.1
//      b1.2
//         b1.2.1
//         b1.2.2
//      b1.3
//   c1
//
   GROUP *root = new GROUP("root");
   root->addmember( new FORM("a1") );

   GROUP *b1 = root->addmember( new GROUP("b1") );
   b1->addmember( new FORM("b1.1") );
   GROUP *b1_2 = b1->addmember( new GROUP("b1.2") );
   b1_2->addmember( new FORM("b1.2.1") );
   b1_2->addmember( new FORM("b1.2.2") );
   b1->addmember( new FORM("b1.3") );

   root->addmember( new FORM("c1") );

   delete b1;   // Delete the subtree at b1.
                // This will not destroy  b1.2.1 or b1.2.2
                // because the base class destructor is used
                // to destroy b1.2 even though b1.2 is a group.
}

Richard Sargent                   Internet: richard@pantor.UUCP
Systems Analyst                   UUCP:     ...!mnetor!becker!pantor!richard

jeffa@hpmwtd.HP.COM (Jeff Aguilera) (01/23/90)

> We have a problem which seems to require virtual destructors.
> Is such a thing valid in C++? 

Certainly.  Didn't you even try

	class X {
		//...

	public:
		virtual ~X();
	};

It won't (oops, it shouldn't, but probably will :-) break your compiler.