称为纯虚方法

时间:2010-01-01 00:47:36

标签: c++ methods pure-virtual

我理解为什么从构造函数调用虚函数很糟糕,但我不确定为什么定义析构函数会导致“纯虚方法调用”异常。代码使用const值来减少动态分配的使用 - 可能也是罪魁祸首。

#include <iostream>
using namespace std;

class ActionBase {
 public:
    ~ActionBase() { } // Comment out and works as expected

    virtual void invoke() const = 0;
};

template <class T>
class Action : public ActionBase {
 public:
    Action( T& target, void (T::*action)())
     : _target( target ), _action( action ) { }

    virtual void invoke() const {
        if (_action) (_target.*_action)();
    }

    T&   _target;
    void (T::*_action)();
};

class View {
 public:
    void foo() { cout << "here" << endl; }
};

class Button : public View {
 public:
    Button( const ActionBase& action )
     : _action( action ) { }

    virtual void mouseDown() {
        _action.invoke();
    }

 private:
    const ActionBase& _action;
};

int main( int argc, char* argv[] )
{
    View view;
    Button button = Button( Action<View>( view, &View::foo ) );
    button.mouseDown();

    return 0;
}

3 个答案:

答案 0 :(得分:6)

你有未定义的行为。因为Button的ctor的参数是const&amp;从一个临时的,它在那条线的尽头被摧毁,就在ctor结束之后。在Action的dtor已经运行之后,您稍后使用_action。由于这是UB,允许实现发生任何事情,显然您的实现会发生一些略微不同的事情,具体取决于您是否在ActionBase中有一个简单的dtor。你得到“纯虚拟调用”消息,因为实现提供了直接调用ActionBase :: invoke的行为,这是当实现在Action的dtor中更改对象的vtable指针时发生的情况。

我建议使用 boost.function 或类似的“动作回调”库(例如,提升signalssignals2。)

答案 1 :(得分:3)

在析构函数上设置一个断点,它将变得清晰。是的,你正在传递一个临时的Action实例&lt;&gt;到Button构造函数。它在按钮构造运行后被销毁。像这样写,问题就消失了:

View view;
Action<View> event(view, &View::foo);
Button button = Button( event ); 
button.mouseDown();

嗯,这不是一个实用的解决方案,事件不会适用于真正的mouseDown调用。 Button构造函数必须创建“event”参数的副本,否则它将需要管理指向委托的指针。

答案 2 :(得分:2)

具有虚函数的类应该始终具有虚析构函数,因此~ActionBase()应该是虚拟的,(~Action()也是如此)。如果您打开更多编译器警告,您将收到有关此警告的警告。

基本上,由于查找规则,析构函数被调用为编译器知道的类型无法实例化(纯虚拟),因此它知道必须出错的地方。

我相信其他人可以比我更好地解释:)