分享

pure virtual fuction(转)

 WUCANADA 2012-06-20

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.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多