在 C++ 中处理指向层次结构中成员函数的指针

Posted

技术标签:

【中文标题】在 C++ 中处理指向层次结构中成员函数的指针【英文标题】:handling pointer to member functions within hierachy in 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)? 或者做类似的事情? 对于这种情况还有哪些其他解决方案?

谢谢。

【问题讨论】:

【参考方案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)));

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

【讨论】:

您能详细说明一下吗?我有点感觉应该有一种方法可以更好地处理所有绑定的东西,但我想不通。

以上是关于在 C++ 中处理指向层次结构中成员函数的指针的主要内容,如果未能解决你的问题,请参考以下文章

从 C++ 的成员函数中获取指向成员函数的指针

在 C++ 中处理类?

C++成员函数指针指向全局函数指针

C++中怎么获取类的成员函数的函数指针

层次结构中的成员函数指针

在 C++ 中调用指向成员函数的指针时出错