模板中调用的虚拟函数不正确

Incorrect virtual function called in templates

本文关键字:虚拟 函数 不正确 调用      更新时间:2023-10-16

我的问题是,当我在传递给回调函数的对象上调用虚拟函数时,调用了错误的函数,并且我得到了运行时错误。

请考虑头文件中的以下代码片段。代码可能无法编译,因为它只是一个片段。

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*