我的问题是,当我在传递给我的回调函数的对象上调用虚函数时,会调用错误的函数,并且会出现运行时错误。
在头文件中考虑以下代码片段。代码可能无法编译,因为它只是一个片段。
class CEventBase1{
protected:
virtual void Show(int code){}
private:
static void _Base1Callback(void*ptr){
CEventBase1* pThis = static_cast<CEventBase1*>(ptr);
pThis->Show(EVENT_CODE);
}
};
class CEventBase2{
protected:
virtual void Move(int code){}
private:
static void _Base2Callback(void*ptr){
CEventBase2* pThis = static_cast<CEventBase2*>(ptr);
pThis-> Move(EVENT_MOVE);
}
};
class CAllEvents: public CEventBase1, public CEventBase2{
};
template<typename EVENTS>
class CWindow : public EVENTS{
};
class CMyEvents: public CAllEvents{
public:
virtual void Move(int code){
// Some processing
}
};
CWindow<CMyEvents> myWin;
此代码将与注册窗口实例的某个库进行交互以处理事件。类似于:
int main () {
SomeLibraryRegisterCallbackData(&myWin);
SomeLibraryRegisterEvent1Callback(CEventBase1::_Base1Callback);
SomeLibraryRegisterEvent2Callback(CEventBase2::_Base2Callback);
return SomeLibraryDispatch();
}
这个想法是,在调度期间,每当注册的事件发生时,指向myWin
的指针就会被传递给已注册的回调。
问题:当程序尝试从静态函数CMyEvents::Move()
调用_Base2Callback()
时,调用CEventBase1::Show()
函数并且程序在调用程序中崩溃Show()
返回时发生错误:
ESP pointer is of incorrect type. this may happen when an incorrect method is called
编译器:Visual C ++ 2012。
答案 0 :(得分:2)
由于您将指向CWindow<CMyEvents>
实例的指针传递给CEventBase2::_Base2Callback
,因此void *
参数向CEventBase2 *
的静态转换是错误的。虽然两者之间存在 is-a 关系,但它是通过多重继承。实际上,这意味着对象布局对于CMyEvents
的实例而言,其地址与从中派生它的CEventBase2
实例的地址不同。
避免此问题的直接方法是避免完全使用void *
。但是,由于您使用的是库,因此必须使代码与框架兼容。在您的情况下,您希望每个基类定义自己的回调函数。这意味着基类需要知道派生类型,因为它是指向传递给回调函数的派生类型的指针。
这可以使用CRTP来完成。使每个基类成为由其派生类参数化的模板类。然后,回调函数可以转换为派生类型:
template <typename DERIVED>
class CEventBase1{
protected:
virtual void Show(int code){}
protected:
static void _Base1Callback(void*ptr){
DERIVED* pThis = static_cast<DERIVED*>(ptr);
pThis->Show(EVENT_CODE);
}
};
template <typename DERIVED>
class CEventBase2{
protected:
virtual void Move(int code){}
private:
static void _Base2Callback(void*ptr){
DERIVED* pThis = static_cast<DERIVED*>(ptr);
pThis-> Move(EVENT_MOVE);
}
};
您还可以使CAllEvents
成为模板类,因此它可以正确地将正确的派生类型传递给基类:
template <typename DERIVED>
class CAllEvents: public CEventBase1<DERIVED>, public CEventBase2<DERIVED>{
};
现在,该类的用户使用它:
class CMyEvents: public CAllEvents<CMyEvents>{
public:
void Move(int code){
// Some processing
}
};
由于定义CWindow<>
的方式,它的实例的地址将与从中派生它的CMyEvents
实例的地址相同。基类中的回调函数会将指针强制转换为CMyEvents*
。