我理解为什么从构造函数调用虚函数很糟糕,但我不确定为什么定义析构函数会导致“纯虚方法调用”异常。代码使用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;
}
答案 0 :(得分:6)
你有未定义的行为。因为Button的ctor的参数是const&amp;从一个临时的,它在那条线的尽头被摧毁,就在ctor结束之后。在Action的dtor已经运行之后,您稍后使用_action。由于这是UB,允许实现发生任何事情,显然您的实现会发生一些略微不同的事情,具体取决于您是否在ActionBase中有一个简单的dtor。你得到“纯虚拟调用”消息,因为实现提供了直接调用ActionBase :: invoke的行为,这是当实现在Action的dtor中更改对象的vtable指针时发生的情况。
我建议使用 boost.function 或类似的“动作回调”库(例如,提升signals和signals2。)
答案 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()
也是如此)。如果您打开更多编译器警告,您将收到有关此警告的警告。
基本上,由于查找规则,析构函数被调用为编译器知道的类型无法实例化(纯虚拟),因此它知道必须出错的地方。
我相信其他人可以比我更好地解释:)