5

我理解为什么从构造函数调用虚函数不好,但我不确定为什么定义析构函数会导致“调用纯虚方法”异常。该代码使用 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;
}
4

3 回答 3

7

您有未定义的行为。 由于 Button 的 ctor 的参数是临时的 const& ,因此它在该行的末尾被销毁,就在 ctor 完成后。在 Action 的 dtor 已经运行之后,您稍后会使用 _action。由于这是 UB,因此允许实现让任何事情发生,并且显然您的实现会根据您在 ActionBase 中是否有一个微不足道的 dtor 来做一些稍微不同的事情。您会收到“纯虚拟调用”消息,因为实现提供了直接调用 ActionBase::invoke 的行为,当实现更改 Action 的 dtor 中对象的 vtable 指针时会发生这种情况。

我建议使用boost.function或类似的“动作回调”库(例如,boost 有信号信号2 )。

于 2010-01-01T01:15:13.230 回答
3

在析构函数上设置一个断点,就会清楚发生了什么。是的,您将 Action<> 的临时实例传递给 Button 构造函数。它在按钮构造运行后被销毁。像这样写,问题就消失了:

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

好吧,这不是一个实际的解决方案,事件不会在真正的 mouseDown 调用范围内。Button 构造函数必须创建“事件”参数的副本,或者必须管理指向委托的指针。

于 2010-01-01T01:22:08.147 回答
2

一个具有虚函数的类应该总是有一个虚析构函数,所以~ActionBase()应该是虚的(也应该是虚的~Action())。如果您打开更多编译器警告,您将收到关于此的警告。

本质上,由于查找规则,为编译器知道无法实例化的类型(纯虚拟)调用析构函数,因此它知道一定有问题。

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

于 2010-01-01T00:54:20.513 回答