在 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_cast
将DEH::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++ 中处理指向层次结构中成员函数的指针的主要内容,如果未能解决你的问题,请参考以下文章