需要重写虚函数来调用基本实现

时间:2012-01-03 00:01:55

标签: c++

在C ++类层次结构中,是否可以强制要求特定虚函数始终调用其基类的实现? (就像构造函数链的方式?)

我正在研究一个深层次层次结构具有一些通用接口函数的情况,每个子类都会覆盖它们。我希望每个派生类'覆盖链接到基类。使用例如下面的代码明确地执行此操作很简单,但是实现新派生类的人可能会忘记链接到基础。

是否有一些模式可以强制执行此操作,如果覆盖无法链接基础,编译器将抛出错误?

所以,在

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!
   }
}

有没有办法,一些模板技巧或语法噱头,让CDD抛出更明显的错误?

6 个答案:

答案 0 :(得分:9)

它的完成方式是基类方法不是虚拟的,并且调用受保护的虚方法。

当然,只处理一个级别。

在您的特定情况下,大量的基础设施可以使其发挥作用,但这不值得。

典型的回应是添加评论

// Always call base class method

答案 1 :(得分:3)

使用私有构造函数在Base类中放置一个特殊的“隐藏”类型,使用friend确保只有Base可以创建它。 This code on ideone

如果有多个级别,遗憾的是这并不能保证调用直接基类。因此struct E : public D;可能会在您E::foo()打电话时调用B:: foo来实施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();
        }
};

如果您尝试在没有返回或使用return Hidden()的情况下实现D :: foo(),则会收到错误消息。编译它的唯一方法是使用return B :: foo()

答案 2 :(得分:3)

遵循一个简单的规则来推导模板类是可能的。

#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;
}

此代码产生(codepad):

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

关于使用typeid的一些答案。我绝不会考虑将typeid用于除调试之外的任何其他事情。这是由于两件事:

  • 动态类型检查可以更有效的方式完成(无需创建type_info对象,即使用dynamic_cast,某些方法
  • C ++标准基本上只保证typeid的存在,但实际上并不是关于它是如何工作的(大多数事情都是“编译器特定的”)

修改

具有多重继承的稍微复杂的示例。 遗憾的是,如果没有从多个基类继承的类中的显式调用,这个问题是不可解决的(主要是因为在这种情况下不清楚应该发生什么,所以我们必须明确定义行为)。

#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 );
}

结果是(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                                       /

答案 3 :(得分:1)

在C ++语言中没有对此的支持,但扩展KerrekSB的评论,你可以这样做:

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;
    }
};

然后像这样使用它

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);
}

输出

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

您必须做一些工作,但您不必在每次调用结束时手动调用baser成员函数。 Here is the live demo

答案 4 :(得分:0)

除了返回某种类型之外,没有任何东西可以直接执行覆盖函数来执行任何操作。但是,如果您创建基类虚函数private,则没有函数可以在基类上调用它,但派生类可以覆盖它。然后,您还提供了一个调用虚函数的public函数以及一个执行基类逻辑的函数。基类中的逻辑可能应该进入一个单独的函数(可能是直接的非虚拟转发函数),以避免在对象实际上恰好是基础对象时执行两次。

答案 5 :(得分:0)

这是一个铿锵有力的检查:https://reviews.llvm.org/rCTE329448(由我制作,所以你可以就此提出任何问题)