【问题标题】:handling pointer to member functions within hierachy in C++在 C++ 中处理指向层次结构中成员函数的指针
【发布时间】:2010-05-26 19:54:11
【问题描述】:

我正在尝试对以下情况进行编码: 我有一个提供处理事件框架的基类。我正在尝试为此使用一组指向成员函数的指针。如下:

class EH { // EventHandler
   virtual void something(); // just to make sure we get RTTI
public:
  typedef void (EH::*func_t)();
protected:
  func_t funcs_d[10];
protected:
  void register_handler(int event_num, func_t f) {
    funcs_d[event_num] = f;
  }
public:
  void handle_event(int event_num) {
    (this->*(funcs_d[event_num]))();
  }
};

那么用户应该从这个类派生其他类并提供处理程序:

class DEH : public EH {
public:
  typedef void (DEH::*func_t)();
  void handle_event_5();
  DEH() {
     func_t f5 = &DEH::handle_event_5;
     register_handler(5, f5); // doesn't compile
     ........
  }
};

此代码无法编译,因为 DEH::func_t 无法转换为 EH::func_t。这对我来说很有意义。在我的情况下,转换是安全的,因为this 下的对象实际上是 DEH。所以我想要这样的东西:

void EH::DEH_handle_event_5_wrapper() {
  DEH *p = dynamic_cast<DEH *>(this);
  assert(p != NULL);
  p->handle_event_5();
}

然后代替

     func_t f5 = &DEH::handle_event_5;
     register_handler(5, f5); // doesn't compile

在 DEH::DEH() 放

     register_handler(5, &EH::DEH_handle_event_5_wrapper); 

所以,最后的问题(花了我足够长的时间......): 有没有办法自动创建这些包装器(如EH::DEH_handle_event_5_wrapper)? 或者做类似的事情? 对于这种情况还有哪些其他解决方案?

谢谢。

【问题讨论】:

    标签: c++ inheritance member-function-pointers


    【解决方案1】:

    您可以简单地使用static_castDEH::func_t 转换为EH::func_t,而不是为所有派生类中的每个处理程序创建一个包装器(当然,这甚至不是一种可行的方法)。成员指针是逆变的:它们自然地向下转换层次结构,并且可以使用static_cast 手动向上转换层次结构(与普通对象指针相反,它们是协变)。

    您正在处理的情况正是 static_cast 功能被扩展以允许成员指针向上转换的原因。此外,成员函数指针的重要内部结构也以这种方式实现,以正确处理此类情况。

    所以,你可以简单地做

    DEH() {
       func_t f5 = &DEH::handle_event_5;
       register_handler(5, static_cast<EH::func_t>(f5));
       ........
    }
    

    我会说,在这种情况下,定义 typedef 名称 DEH::func_t 毫无意义——它毫无用处。如果您删除DEH::func_t 的定义,典型的注册码将如下所示

    DEH() {
       func_t f5 = static_cast<func_t>(&DEH::handle_event_5); 
       // ... where `func_t` is the inherited `EH::func_t`
       register_handler(5, f5);
       ........
    }
    

    为了让它看起来更优雅,您可以在DEH 中为register_handler 提供一个包装器,或者使用其他方式(宏?模板?)来隐藏演员表。

    此方法没有为您提供任何方法来验证调用时处理程序指针的有效性(就像您可以在基于包装器的版本中使用 dynamic_cast 一样)。我不知道你有多在乎这个检查到位。我会说,在这种情况下,它实际上是不必要和过度的。

    【讨论】:

    • 谢谢。它解决了这个问题。我知道演员表是可能的,但认为根据标准它具有未定义的行为。
    • @anatoli:不,行为已定义,假设调用有效。同样,调用的有效性在调用的那一刻确定,即如果对象具有正确的类型并且指针指向正确的方法,那么一切正常。标准中是这样定义的。
    【解决方案2】:

    为什么不直接使用虚函数呢?类似的东西

    class EH {
    public:
      void handle_event(int event_num) {
    
        // Do any pre-processing...
    
        // Invoke subclass hook
        subclass_handle_event( event_num );
    
        // Do any post-processing...
      }
    private:
      virtual void subclass_handle_event( int event_num ) {}
    };
    
    class DEH : public EH {
    public:
      DEH() { }
    private:
      virtual void subclass_handle_event( int event_num ) {
         if ( event_num == 5 ) {
            // ...
         }
      }
    };
    

    【讨论】:

    • 有可能。然而,我想让 DEH 的实现对用户来说尽可能简单——这个对所有事件的“如果”将非常难看。
    • @anatoli:所以你认为创建派生类的用户会发现指向成员的指针比 if 或 switch 构造更简单?
    • 抱歉,我掉线了 - 帐户出了点问题。我同意你的观点,指向成员的指针非常讨厌。但是,我认为在这种情况下,大部分内容都在幕后。用户注册处理程序的方式非常简单,并且遵循非常简单的模板。同样,与 if/switch 解决方案相反,在这种情况下,基类可以进行更多检查 - 例如是否有所有事件的处理程序等。
    【解决方案3】:

    你真的不应该这样做。查看 boost::bind

    http://www.boost.org/doc/libs/1_43_0/libs/bind/bind.html

    阐述

    首先,我敦促您重新考虑您的设计。我见过的大多数事件处理程序系统都包含一个外部注册对象,该对象维护事件到处理程序对象的映射。您在 EventHandler 类中嵌入了注册,并且正在基于函数指针进行映射,这是不可取的。您遇到了问题,因为您正在围绕内置的虚函数行为结束运行。

    boost::bind 之类的要点是从函数指针中创建对象,允许您利用面向对象的语言特性。因此,以您的设计为起点的基于boost::bind 的实现将如下所示:

    struct EventCallback
    {
        virtual ~EventCallback() { }
        virtual void handleEvent() = 0;
    };
    
    template <class FuncObj>
    struct EventCallbackFuncObj : public IEventCallback
    {
        EventCallbackT(FuncObj funcObj) :
            m_funcObj(funcObj) { }
        virtual ~EventCallbackT() { }
    
        virtual void handleEvent()
        {
            m_funcObj();
        }
    
        private:
            FuncObj m_funcObj;
    };
    

    那么你的register_handler 函数看起来像这样:

      void register_handler(int event_num, EventCallback* pCallback) 
      {
          m_callbacks[event_num] = pCallback;
      }
    

    您的注册电话希望:

    register_handler(event, 
        new EventCallbackFuncObj(boost::bind(&DEH::DEH_handle_event_5_wrapper, this)));
    

    现在您可以从任何类型的(对象,成员函数)创建回调对象,并将其保存为给定事件的事件处理程序,而无需编写自定义函数包装对象。

    【讨论】:

    • 您能详细说明一下吗?我有点感觉应该有一种方法可以更好地处理所有绑定的东西,但我想不通。
    猜你喜欢
    • 1970-01-01
    • 2017-03-22
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-26
    • 1970-01-01
    相关资源
    最近更新 更多