C++ 纯虚拟方法调用

C++ 纯虚拟方法调用,c++,methods,pure-virtual,C++,Methods,Pure Virtual,我理解为什么从构造函数调用虚函数是不好的,但我不确定为什么定义析构函数会导致“纯虚方法调用”异常。代码使用const值来减少动态分配的使用,这可能也是罪魁祸首 #include <iostream> using namespace std; class ActionBase { public: ~ActionBase() { } // Comment out and works as expected virtual void invoke() const =

我理解为什么从构造函数调用虚函数是不好的,但我不确定为什么定义析构函数会导致“纯虚方法调用”异常。代码使用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;
}
#包括
使用名称空间std;
类ActionBase{
公众:
~ActionBase(){}//注释掉并按预期工作
虚拟void invoke()常量=0;
};
模板
集体诉讼:公共诉讼基础{
公众:
行动(T&target,无效(T:::*行动)()
:_target(target),_action(action){}
虚拟void invoke()常量{
如果(_action)(_target.*u action)();
}
T&u目标;
无效(T::*_action)();
};
类视图{
公众:

void foo(){cout具有虚拟函数的类应该始终具有虚拟析构函数,因此
~ActionBase()
应该是虚拟的,(并且
~Action()
也应该是虚拟的)。如果启用“更多编译器警告”,则会收到有关此问题的警告

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


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

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


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

在析构函数上设置一个断点,它将清楚地显示发生了什么。是的,您正在向按钮构造函数传递一个临时操作实例。在按钮构造函数运行后,它将被销毁。这样编写,问题就会消失:

View view;
Action<View> event(view, &View::foo);
Button button = Button( event ); 
button.mouseDown();
视图;
操作事件(视图和视图::foo);
按钮=按钮(事件);
button.mouseDown();

这不是一个实用的解决方案,事件不在真正的mouseDown调用的范围内。按钮构造函数必须创建“事件”的副本参数,否则它将不得不管理一个指向委托的指针。

您应该将
ActionBase
的析构函数
virtual
。请参阅向操作添加一个析构函数,该析构函数在被销毁时打印出来,您将看到它在可以使用之前死亡,从而调用未定义的行为。虽然您的建议非常正确,但这确实是正确的sn与主题中的问题没有任何关系。感谢大家的快速回答。使用虚拟析构函数,它仍然使用“调用的纯虚拟方法”编译和执行gcc-ubuntu-64 4.3.3出现错误。第一个产生错误,下一个不产生错误。两者之间有什么本质区别?第二个获取临时地址,这当然不好。第一个是否也有?按钮按钮=按钮(操作(视图和视图::foo));操作操作(视图和视图::foo);按钮2=按钮(操作);Mike:问题不是获取临时对象的地址,而是在其引用的对象的生存期结束后使用引用(参见我的答案)。关于未定义性…我检查了程序集,如果析构函数是“微不足道”的,那么gcc似乎根本没有破坏
操作
层次结构--i、 编译器生成。因此,代码在没有析构函数的情况下正确捕获了vtable指针,因为它在内存中未重写。没有正确捕获它;这是未定义的行为,并且整个程序中的所有赌注都被取消(根据标准保证)。不过,您的发现是有意义的,在普通的dtor情况下,vtable指针没有更新,因此没有错误消息。恶魔从鼻子里飞出来等等。谢谢罗杰,现在我了解了发生的事情。我最近才开始对非普通代码使用const,所以我并不完全清楚操作的范围。+1。但是为什么‘action event=AAction(视图和视图::foo)“?为什么不“Action event(视图和视图::foo)”;