GotW #31

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed March 25: We Have FDIS! (Trip Report: March 2011 C++ Standards Meeting)
March 24:
Book on PPL Is Now Available
January 14: Interview on Channel 9
December 31: 2010: Cyberpunk World

This is the original GotW problem and solution substantially as posted to Usenet. See the book More Exceptional C++ (Addison-Wesley, 2002) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

(Im)pure Virtual Functions
Difficulty: 7 / 10

Does it ever make sense to make a function pure virtual, but still provide a body?

Problem

JG Question

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

Guru Question

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

Solution

1. 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

2. 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(); // 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. :-)

 

Notes

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.

Copyright © 2011 Herb Sutter