-4

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

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

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。

4

1 回答 1

2

由于您正在传递指向CWindow<CMyEvents>实例的指针,因此参数CEventBase2::_Base2Callback的静态转换是错误的。虽然两者之间存在is-a关系,但它是通过多重继承来实现的。实际上,这意味着对象布局是这样的,对于 的实例,其地址与派生它的实例的地址不同。void *CEventBase2 *CMyEventsCEventBase2

避免这个问题的直接方法是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*.

于 2013-10-10T21:45:16.713 回答