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

时间:2013-10-10 20:57:58

标签: c++ templates visual-c++

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

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

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。

1 个答案:

答案 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*