[comp.std.c++] Pure virtual destructors: good or bad idea?

shankar@hpclscu.HP.COM (Shankar Unni) (09/13/90)

PURE VIRTUAL DESTRUCTORS:

One of our users came up with an interesting construct:

   class Base {
     virtual ~Base() = 0;	// pure virtual destructor
   };
   
   class Derived { /*...*/ };
   
   // ...
   Derived *p = new Derived();
   delete p;
   // ...

The problem is, cfront does not like pure virtual destructors. In fact, my
reading of the 2.1 ARM (section 12.4) seems to imply that since
"destructors cannot be inherited", such code should not make sense, because
making a function pure virtual means that you are forcing it to be
redefined in each of its derived classes, and since a destructor cannot be
redefined in a base class, this should be meaningless for destructors.

Indeed, it doesn't work: cfront emits a call to "Base::~Base()" when
deleting "p", which leads to a link-time unresolved).

The key point seems to be: the language insists that every class have a
destructor. There is *no way* for a user to say: "I don't want a destructor
function for this class. Just return the object to memory when "delete" is
called on this object".  Every destruction of an object unconditionally
generates a call to the destructor of each of its base classes, whether or
not they are "pure virtual".

How good (or bad) an idea is it to support such an extension to the meaning
of "= 0" on a virtual member function declaration? I mean, if the user
specifies a destructor as "pure virtual", then:

   (a) don't check for redefinitions in inherited classes (cfront doesn't
       do this now, anyway), *and*,
   (b) don't emit any calls to that destructor, when an object of a class
       inherited from it is destroyed.

Seems easy enough to do - is there any major semantic problem with this?
-----
Shankar Unni                                   E-Mail: 
Hewlett-Packard California Language Lab.     Internet: shankar@hpda.hp.com
Phone : (408) 447-5797                           UUCP: ...!hplabs!hpda!shankar

shopiro@alice.UUCP (Jonathan Shopiro) (09/14/90)

In article <77210003@hpclscu.HP.COM>, shankar@hpclscu.HP.COM (Shankar Unni) writes:
> PURE VIRTUAL DESTRUCTORS:
> 
> One of our users came up with an interesting construct:
> 
>    class Base {
>      virtual ~Base() = 0;	// pure virtual destructor
	virtual	void	f() = 0;  // example pure virtual function
>    };
>    
>    class Derived { /*...*/ };

If you want to do this you must define the Base destructor, e.g.

	Base::~Base() {}

It is a little-known (and perhaps unfortunate) fact that pure virtual
functions can be defined.  They can only be called through the
explicitly qualified name, e.g.,

	Base*	bp = new Derived;
	bp->Base::f();

would call Base::f() if it has been defined, and cause a link error
otherwise.  The destructor is a slightly special case (:-)), since the
compiler automatically generates the equivalent of an explicitly
qualified call to it in the destructor of the derived class.

An alternative way to define the language would be to disallow
defining pure virtual functions and (therefore) disallow pure virtual
destructors.  This would give the programmer somewhat less flexibility
but would not leave the compiler wondering whether a pure virtual
function was going to be defined or not.
-- 
		Jonathan E. Shopiro
		AT&T Bell Laboratories, Warren, NJ  07059-0908
		shopiro@research.att.com   (201) 580-4229

steve@taumet.com (Stephen Clamage) (09/14/90)

shankar@hpclscu.HP.COM (Shankar Unni) writes:

>PURE VIRTUAL DESTRUCTORS:

>One of our users came up with an interesting construct:

>   class Base {
>     virtual ~Base() = 0;	// pure virtual destructor
>   };

I really don't see what problem this user is trying to solve.  No class
is required to have a destructor, so if you don't want a destructor
called for class Base, don't define one.

Virtual destructors are a fine idea.  To enforce virtual destructors
in a hierarchy, put an empty virtual destructor in the most base class:
	class Base { ... virtual ~Base(){} ... };

If you wish to create an abstract class, pure virtual functions are a
big help.  Define the abstractions to be realized in derived classes
as pure virtual in the base class.  The destructor is not such a
candidate.

If a base class has a destructor, it must be called when a derived class
is destroyed; if the destructor is pure virtual it cannot be called.
Messing up the semantics of destructors to accomodate this idea would
have to be compensated by some real advantage.  I can't see that there
is any advantage in pure virtual destructors.
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

shankar@hpclscu.HP.COM (Shankar Unni) (09/18/90)

> It is a little-known (and perhaps unfortunate) fact that pure virtual
> functions can be defined.  They can only be called through the
> explicitly qualified name, e.g.,
> 
> 	Base*	bp = new Derived;
> 	bp->Base::f();
> 
> would call Base::f() if it has been defined, and cause a link error
> otherwise.  The destructor is a slightly special case (:-)), since the
> compiler automatically generates the equivalent of an explicitly
> qualified call to it in the destructor of the derived class.
> 
> An alternative way to define the language would be to disallow
> defining pure virtual functions and (therefore) disallow pure virtual
> destructors.  This would give the programmer somewhat less flexibility
> but would not leave the compiler wondering whether a pure virtual
> function was going to be defined or not.

Yes, I remember filing a bug report about this, not realizing it was a
feature :-(.

However, the point (and it's not one that I'm going to beat around a lot -
this was mostly an information query), I think, was that he wanted to have
a destructor defined in a way such that *nothing* was called on behalf of
the base class component of the object.

Stephen Clamage writes:

> I really don't see what problem this user is trying to solve.  No class
> is required to have a destructor, so if you don't want a destructor
> called for class Base, don't define one.

But that's precisely the problem: if you don't define a destructor for a
class, *cfront will define one for you*; and call it when the object is
destroyed. This destructor happens to be a null inline, so when destroying
a standalone object of that class, nothing happens; however, when
destroying an object of a derived class, when cfront gets around to the
base class component, it puts out an out-of-line call to the null
destructor (because it's virtual).

Anyway, never mind; I'm convinced...
----
Shankar Unni.

mike@taumet.com (Michael S. Ball) (09/19/90)

In article <77210004@hpclscu.HP.COM> shankar@hpclscu.HP.COM (Shankar Unni) writes:
>But that's precisely the problem: if you don't define a destructor for a
>class, *cfront will define one for you*; and call it when the object is
>destroyed. This destructor happens to be a null inline, so when destroying
>a standalone object of that class, nothing happens; however, when
>destroying an object of a derived class, when cfront gets around to the
>base class component, it puts out an out-of-line call to the null
>destructor (because it's virtual).

Base class destructors called from within a derived class destructor are
not called using the virtual mechanism.  It's the equivalent of calling
bv->B::foo() instead of b->foo().  If you saw an out-of-line call I
suspect you ran into the cfront hack which prohibits inlining in
expressions of a certain complexity (I believe no more than 2 inlines
per expression, though I may have the number wrong.)  If you saw an
out-of-line virtual call, you ran into a deadly bug which will cause
all virtual destructors to die the recursive death.

In any case, you can't make the destructor virtual unless you define it.
The compiler generated constructor won't be virtual unless it has a
base class with a virtual destructor.  Defining a pure virtual destructor
with an inline empty body will also work, though, as mentioned above,
cfront may not optimize it away.

Mike Ball
TauMetric Corporation