TITLE: defining pure virtuals

(Newsgroups: comp.lang.c++.moderated, 2 Mar 98)

AUTHOR: herbs@cntc.com (Herb Sutter)

** Note: GotW Archive Site Being Updated

The GotW archive site has been revamped, and will be
available again soon. We now return you to your
regularly scheduled GotW issue, already in progress...


 .--------------------------------------------------------------------.
 |  Guru of the Week problems and solutions are posted regularly on   |
 |   news:comp.lang.c++.moderated. For past problems and solutions    |
 |            see the GotW archive at http://www.cntc.com.            |
 | Is there a topic you'd like to see covered? mailto:herbs@cntc.com  |
 `--------------------------------------------------------------------'
_______________________________________________________

GotW #31:   (Im)pure Virtual Functions

Difficulty: 7 / 10
_______________________________________________________


>What is a pure virtual function?  Give an example.

A pure virtual function is a virtual function that you
want to force derived classes to override.  If a class
has any unoverridden pure virtuals, it is an "abstract
class" and you can't create objects of that type.

    class AbstractClass {
    public:
        // declare a pure virtual function:
        // this class is now abstract
        virtual void f(int) = 0;
    };

    class StillAbstract : public AbstractClass {
        // does not override f(int),
        // so this class is still abstract
    };

    class Concrete : public StillAbstract {
    public:
        // finally overrides f(int),
        // so this class is concrete
        void f(int) { /*...*/ }
    };

    AbstractClass a;    // error, abstract class
    StillAbstract b;    // error, abstract class
    Concrete      c;    // ok, concrete class

_______________________________________________________

>Why might you declare a pure virtual function and also
>write a definition (body)?  Give as many reasons or
>situations as you can.

There are three main reasons you might do this.  #1 is
commonplace, #2 is pretty rare, and #3 is a workaround
used occasionally by advanced programmers working with
weaker compilers.

Most programmers should only ever use #1.


1. Pure Virtual Destructor
--------------------------

All base classes should have a virtual destructor (see
your favourite C++ book for the reasons).  If the class
should be abstract (you want to prevent instantiating
it) but it doesn't happen to have any other pure
virtual functions, a common technique to make the
destructor pure virtual:

    // file b.h
    class B {
    public: /*...other stuff...*/
        virtual ~B() = 0; // pure virtual dtor
    };

Of course, any derived class' destructor must call the
base class' destructor, and so the destructor must
still be defined (even if it's empty):

    // file b.cpp
    B::~B() { /* possibly empty */ }

If this definition were not supplied, you could still
derive classes from B but they could never be
instantiated, which isn't particularly useful.


2. Force Conscious Acceptance of Default Behaviour
--------------------------------------------------

If a derived class doesn't choose to override a normal
virtual, it just inherits the base version's behaviour
by default.  If you want to provide a default behaviour
but not let derived classes just inherit it "silently"
like this, you can make it pure virtual but still
provide a default that the derived class author has to
call deliberately if he wants it:

    class B {
    public:
        virtual bool f() = 0;
    };

    bool B::f() {
        return true;  // this is a good default, but
    }                 // shouldn't be used blindly

    class D : public B {
    public:
        bool f() {
            return B::f(i); // if D wants the default
        }             // behaviour, it has to say so
    };


3. Workaround Poor Compiler Diagnostics
---------------------------------------

There are situations where you could accidentally end
up calling a pure virtual function (indirectly from a
base constructor or destructor; see your favourite
advanced C++ book for examples).  Of course,
well-written code normally won't get into these
problems, but no one's perfect and once in a while it
happens.

Unfortunately, not all compilers[1] will actually tell
you when this is the problem.  Those that don't can
give you spurious unrelated errors that take forever to
track down.  "Argh," you scream, when you do finally
diagnose the problem yourself some hours later, "why
didn't the compiler just tell me that's what I did?!"

One way to protect yourself against this wasted
debugging time is to provide definitions for the pure
virtual functions that should never be called, and put
some really evil code into those definitions that lets
you know right away if you do call them accidentally.
For example:

    class B {
    public:
        virtual bool f() = 0;
    };

    bool B::f() {   // this should NEVER be called
        if( PromptUser( "pure virtual B::f called -- "
                        "abort or ignore?" ) == Abort )
            DieDieDie();
    }

In the common DieDieDie() function, do whatever is
necessary on your system to get into the debugger or
dump a stack trace or otherwise get diagnostic
information.  Here are some common methods that will
get you into the debugger on most systems.  Pick the
one that you like best.

    void DieDieDie() {  // scribble through null ptr
        memset( 0, 1, 1 );
    }

    void DieDieDie() {  // another C-style method
        assert( false );
    }

    void DieDieDie() {  // back to last "catch(...)"
        class LocalClass {};
        throw LocalClass();
    }

    void DieDieDie() {  // for standardphiles
        throw logic_error();
    }

You get the idea.  Be creative.  :-)



[1] Well, technically it's the runtime environment that
catches this sort of thing.  I'll say "compiler" anyway
because it's generally the compiler that ought to slip
in the code that checks this for you at runtime.

