Requiring overridden virtual functions to call bas

2020-07-02 03:11发布

Within a C++ class hierarchy, is it possible to enforce a requirement that a particular virtual function always call its base class's implementation also? (Like the way constructors chain?)

I'm looking at a case where a deep class hierarchy has some common interface functions which each child overrides. I'd like each derived class' override to chain through to the base class. It's straightforward to do this explicitly with eg the code below, but there's the risk that someone implementing a new derived class might forget to chain through to the base.

Is there some pattern to enforce this, such that the compiler will throw an error if an override fails to chain the base?

So, in

class CAA 
{
   virtual void OnEvent( CEvent *e ) { 
     // do base implementation stuff;
   }
}

class CBB : public CAA
{
   typedef CAA BaseClass;
   virtual void OnEvent( CEvent *e ) { 
       DoCustomCBBStuff();
       BaseClass::OnEvent( e ); // chain to base
   }
}

class CCC : public CBB
{
   typedef CBB BaseClass;
   virtual void OnEvent( CEvent *e ) { 
       Frobble();
       Glorp();
       BaseClass::OnEvent( e ); // chain to CBB which chains to CAA, etc
   }
}

class CDD : public CCC
{
   typedef CCC BaseClass;
   virtual void OnEvent( CEvent *e ) { 
       Meep();
       // oops! forgot to chain to base!
   }
}

is there a way, some template trick or syntactic gimmick, to make CDD throw a more obvious error?

标签: c++
6条回答
萌系小妹纸
2楼-- · 2020-07-02 03:15

The way its done is the base class method is not virtual and calls a protected virtual method.

Of course, that only handles one level.

In your particular situation, a large amount of infrastructure can make it work, but it's not worth it.

The typical response is to add a comment

// Always call base class method
查看更多
狗以群分
3楼-- · 2020-07-02 03:15

Put a special 'hidden' type in the Base class with a private constructor, using friend to ensure that only the Base can create it. This code on ideone.

If there are multiple levels, this unfortunately does not guarantee that the immediate base class is called. Hence struct E : public D; could implement E::foo() with a call to B:: foo when you might prefer a call to D::foo().

struct Base {
        struct Hidden {
                friend class Base;
                private:
                        Hidden() {}
        };
        virtual Hidden foo() {
                cout << "Base" << endl;
                return Hidden(); // this can create a Hidden
        }
};

struct D : public B {
        virtual Hidden foo() {
                cout << "D" << endl;
                // return Hidden(); // error as the constructor is private from here
                return B :: foo();
        }
};

If you tried to implement D :: foo() without a return or with return Hidden(), you will get an error message. The only way to compile this is to use return B :: foo().

查看更多
迷人小祖宗
4楼-- · 2020-07-02 03:22

There's a clang-tidy check for this: https://reviews.llvm.org/rCTE329448 (made by me, so you may address me any questions on it)

查看更多
别忘想泡老子
5楼-- · 2020-07-02 03:31

There isn't anything directly enforcing an overriding function to do anything in particular, except returning a certain type. However, if you make the base class virtual function private no function can call it on the base class but derived classes can override it. You then also provide a public function which calls the virtual function as well as a function doing the base class logic. The logic from the base class probably should go into a separate function (possibly the non-virtual forwarding function directly) to avoid executing it twice if the object actually happens to be a base object.

查看更多
我欲成王,谁敢阻挡
6楼-- · 2020-07-02 03:33

Following a simple rule to derive through a template class it is possible.

#include <iostream>

struct TEvent
{
};

struct Base {
    virtual void CallOnEvent(TEvent * e)
    {
        OnEvent(e);
    }
    virtual void OnEvent(TEvent * e)
    {
        std::cout << "Base::Event" << std::endl;
    }
    void CallUp(TEvent * e)
    {
    }

};

template <typename B>
struct TDerived : public B
{
    void CallUp( TEvent * e )
    {
        B::CallUp(e);
        B::OnEvent(e);
    }
    virtual void CallOnEvent( TEvent * e )
    {
        CallUp(e);
        this->OnEvent(e);
    }
};

struct Derived01 : public TDerived< Base >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived01::Event" << std::endl;
    }
};

struct Derived02 : public TDerived< Derived01 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived02::Event" << std::endl;
    }
};

struct Derived03 : public TDerived< Derived02 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived03::Event" << std::endl;
    }
};

struct Derived04 : public TDerived< Derived03 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived04::Event" << std::endl;
    }
};


int main( void )
{
 Derived04 lD4;
 lD4.CallOnEvent(0);
 return 0;
}

This code yields (codepad):

Base::Event
Derived01::Event
Derived02::Event
Derived03::Event
Derived04::Event

Regarding some answers using typeid. I would never consider using typeid for anything else than debugging. This is due to two things:

  • dynamic type checking can be done in a much more efficient ways (without creating type_info object i.e. using dynamic_cast, some methods
  • C++ standard basically guarantees only the existance of typeid, but not really anything regarding how it works (most things are "compiler specific")

edit:

A slightly more complex example with multiple inheritance. This one unfortunately is not solvable without explicit calls in classes that do inherit from multiple bases (mainly because it is not clear what should happen in such cases, so we have to explicitly define the behaviour).

#include <iostream>

struct TEvent
{
};

struct Base {
    virtual void CallOnEvent(TEvent * e)
    {
        OnEvent(e);
    }
    virtual void OnEvent(TEvent * e)
    {
        std::cout << "Base::Event" << std::endl;
    }

    void CallUp(TEvent * e)
    {
    }
};

template <typename B >
struct TDerived : public B
{
    void CallUp( TEvent * e )
    {
        B::CallUp(e);
        B::OnEvent(e);
    }
    virtual void CallOnEvent( TEvent * e )
    {
        CallUp(e);
        this->OnEvent(e);
    }
};

struct Derived01 : virtual public TDerived< Base >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived01::Event" << std::endl;
    }
};

struct Derived02 : virtual public TDerived< Derived01 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived02::Event" << std::endl;
    }
};

typedef TDerived< Derived02 > TDerived02;
typedef TDerived< Derived01 > TDerived01;
struct Derived03 : virtual public TDerived02, virtual public TDerived01
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived03::Event" << std::endl;
    }

    virtual void CallOnEvent( TEvent * e )
    {
        CallUp(e);
        Derived03::OnEvent(e);
    }
    void CallUp( TEvent * e )
    {
        TDerived02::CallUp(e);
        TDerived01::CallUp(e);
    }
};

struct Derived04 : public TDerived< Derived03 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived04::Event" << std::endl;
    }
};


int main( void )
{
 Derived04 lD4;
 Derived03 lD3;

 lD3.CallOnEvent( 0 );
 std::cout << std::endl;
 lD4.CallOnEvent( 0 );

 return ( 0 );
}

Result is (ideone):

Base::Event      \                  \
Derived01::Event | - from Derived02 |
Derived02::Event /                  |-- from Derived03
Base::Event      \__ from Derived01 |
Derived01::Event /                  |
Derived03::Event                    /

Base::Event      \                  \                  \
Derived01::Event | - from Derived02 |                  |
Derived02::Event /                  |-- from Derived03 |-- from Derived04
Base::Event      \__ from Derived01 |                  |
Derived01::Event /                  |                  |
Derived03::Event                    /                  |
Derived04::Event                                       /
查看更多
SAY GOODBYE
7楼-- · 2020-07-02 03:36

There is no support for this in the C++ language, but expanding on KerrekSB's comment, you could do something like this:

class A {
public:
    void DoEvent(int i) {
        for (auto event = events.begin(); event != events.end(); ++event)
            (this->*(*event))(i);
    }

protected:
    typedef void (A::*Event)(int);

    A(Event e) {
        events.push_back(&A::OnEvent);
        events.push_back(e);
    }

    void OnEvent(int i) {
        cout << "A::OnEvent " << i << endl;
    }

    vector<Event> events;
};

class B : public A {
public:
    B() : A((Event)&B::OnEvent) { }

protected:
    B(Event e) : A((Event)&B::OnEvent) {
        events.push_back(e);
    }

    void OnEvent(int i) {
        cout << "B::OnEvent " << i << endl;
    }
};

class C : public B {
public:
    C() : B((Event)&C::OnEvent) { }

protected:
    C(Event e) : B((Event)&C::OnEvent) {
        events.push_back(e);
    }

    void OnEvent(int i) {
        cout << "C::OnEvent " << i << endl;
    }
};

Then use it like this

int main() {
    A* ba = new B;
    ba->DoEvent(32);

    B* bb = new B;
    bb->DoEvent(212);

    A* ca = new C;
    ca->DoEvent(44212);

    B* cb = new C;
    cb->DoEvent(2);

    C* cc = new C;
    cc->DoEvent(9);
}

That outputs

A::OnEvent 32
B::OnEvent 32

A::OnEvent 212
B::OnEvent 212

A::OnEvent 44212
B::OnEvent 44212
C::OnEvent 44212

A::OnEvent 2
B::OnEvent 2
C::OnEvent 2

A::OnEvent 9
B::OnEvent 9
C::OnEvent 9

You have to do a little work, but you don't have to manually call the baser member function at the end of each call. Here is the live demo.

查看更多
登录 后发表回答