【问题标题】:Requiring overridden virtual functions to call base implementations需要重写的虚函数来调用基本实现
【发布时间】:2012-02-01 03:46:52
【问题描述】:

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

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

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

所以,在

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

【问题讨论】:

标签: c++


【解决方案1】:

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

当然,这只处理一个级别。

在你的特定情况下,大量的基础设施可以让它工作,但这并不值得。

典型的反应是添加评论

// Always call base class method

【讨论】:

    【解决方案2】:

    使用私有构造函数在 Base 类中放置一个特殊的“隐藏”类型,使用 friend 确保只有 Base 可以创建它。 This code on 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();
            }
    };
    

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

    【讨论】:

    • 可爱...但略有缺陷。 foo() 应该返回一个 const 引用,并且您应该防止复制 Hidden。当然,您的解决方案已经很好地防止了意外遗漏,尤其是将Hidden重命名为DoNotForgetToChain或类似的东西。
    【解决方案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                                       /
    

    【讨论】:

      【解决方案4】:

      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.

      【讨论】:

      • 这是我暗示的工作量太大,但确实有效。
      • @Joshua 不幸的是,这是你能做的最好的事情,而不是仅仅诉诸惯例。
      【解决方案5】:

      除了返回某种类型之外,没有任何东西直接强制覆盖函数来做任何特别的事情。但是,如果您将基类设为虚函数private,则任何函数都不能在基类上调用它,但派生类可以覆盖它。然后,您还提供了一个调用虚函数的public 函数以及一个执行基类逻辑的函数。来自基类的逻辑可能应该进入一个单独的函数(可能直接是非虚拟转发函数)以避免在对象实际上是基对象时执行两次。

      【讨论】:

      • 这样做的问题是它只涵盖了一个层次——即所有派生调用都会调用最终的基函数,但所有中间类都不参与该方。
      • @Xeo:同意。我不知道任何强制链接虚拟功能的方法。尽管我认为链接覆盖函数的想法很可爱(例如在 Xt 中使用,它是一个用 C 实现的 X11 的面向对象的 GUI 系统;所有面向对象的东西都需要手动完成),但我不需要任何东西构造和破坏碰巧被锁住了。
      【解决方案6】:

      有一个clang-tidy检查:https://reviews.llvm.org/rCTE329448(由我制作,所以你可以向我提出任何问题)

      【讨论】:

        猜你喜欢
        • 2013-06-02
        • 2012-09-21
        • 2010-10-15
        • 1970-01-01
        • 1970-01-01
        • 2014-02-02
        • 2021-05-09
        • 2015-06-28
        • 1970-01-01
        相关资源
        最近更新 更多