模板中调用的虚拟函数不正确
Incorrect virtual function called in templates
我的问题是,当我在传递给回调函数的对象上调用虚拟函数时,调用了错误的函数,并且我得到了运行时错误。
请考虑头文件中的以下代码片段。代码可能无法编译,因为它只是一个片段。
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
注册的事件时,指向的指针都会传递给已注册的回调。
问题:当程序尝试从静态函数_Base2Callback()
调用CMyEvents::Move()
时,CEventBase1::Show()
函数被调用,并且程序在调用方函数中崩溃,一旦返回错误Show()
:
ESP pointer is of incorrect type. this may happen when an incorrect method is called
编译器:Visual C++ 2012。
由于您正在传递指向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*
。
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 类型擦除的std::function与虚拟函数调用的开销
- 重写虚拟函数和继承
- 用纯虚拟函数兜圈子
- 为什么使用存储在虚拟方法表中的地址调用虚拟函数的函数会返回垃圾?
- 禁止子函数调用父级的抽象(或虚拟)函数
- 无法在子类中使用虚拟函数C++
- 无法在派生对象上运行虚拟函数
- 我可以调用从 main() 覆盖的虚拟函数吗?
- 在 C++ 中将函数获取和设置为虚拟函数
- 使用在堆栈上创建的对象调用虚拟函数
- 为什么在这种情况下不调用我的虚拟函数实现?
- 在C++中使虚拟函数私有化
- 模板继承类中的虚拟函数
- 为什么构造函数的虚拟函数调用有时有效,但其他调用却无效
- doxygenc++虚拟函数和实现
- 如何从派生类函数中调用虚拟函数