要求重写的虚函数调用基础实现

17
在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抛出更明显的错误?


1
据我所知没有,但您可以给基类一个非虚函数并存储一个函数指针列表,每个派生类都向其中添加其部分。 - Kerrek SB
这实际上是一个重复的问题:如何强制子类在调用其父类虚函数之前调用相同的虚函数? - James McNellis
可能是C++:如何自动调用基类方法?的重复问题。 - outis
6个回答

9
这种做法是基类方法不是虚函数,而是调用了一个受保护的虚函数。
当然,这仅仅处理了一层继承关系。
在你特定的情况下,大量的基础设施可以使它正常工作,但这并不值得。
典型的解决方案是添加注释。
// Always call base class method

3
在Base类中放置一个特殊的“hidden”类型,使用友元将其构造函数设置为私有,以确保只有Base可以创建它。此代码在ideone上
如果存在多层继承,则不幸的是这不能保证立即基类被调用。因此,struct E : public D; 可能会使用对B::foo的调用来实现 E::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();
        }
};

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


可爱的...但有些缺陷。foo() 应该返回一个 const 引用,并且您应该防止复制 Hidden。当然,您的解决方案已经很好地防止了意外省略,尤其是将 Hidden 重命名为 DoNotForgetToChain 或类似的名称。 - Matthieu M.

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                                       /

2

在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

你需要做一些工作,但你不必在每次调用结束时手动调用基础成员函数。 这里是实时演示

这就是我所提到的工作量太大,但它确实有效。 - Joshua
很不幸,@Joshua,这是你能做的最好的事情,除非你只是依靠传统。 - Seth Carnegie

0

没有什么直接强制覆盖函数去做任何特定的事情,除了返回某种类型。然而,如果你将基类虚函数设为private,那么没有函数可以在基类上调用它,但派生类可以重写它。然后,您还提供一个public函数来调用虚函数以及执行基类逻辑的函数。基类的逻辑可能应该放到一个单独的函数中(可能是非虚拟转发函数),以避免在对象实际上是基础对象时执行两次。


问题在于它只涵盖了一个级别 - 即所有派生调用都将调用最终基础函数,但所有中间类都不参与其中。 - Xeo
@Xeo:同意。我不知道任何强制链接虚函数的方法。虽然我认为链接覆盖函数的想法很可爱(例如在C语言中实现的X11面向对象GUI系统Xt中使用),但我只需要构造和析构,这些恰好是链接的。 - Dietmar Kühl

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接