【问题标题】:C++ "Virtual functions but no virtual destructors"C ++“虚拟函数但没有虚拟析构函数”
【发布时间】:2012-04-18 23:14:27
【问题描述】:

我有一个基类Media 和几个派生类,即DVD、Book 等... 基类写成:

class Media{
    private:
        int id;
        string title;
        int year;
    public:
        Media(){ id = year = 0; title = ""; }
        Media(int _id, string _title, int _year): id(_id), title(_title), year(_year) {}
//      virtual ~Media() = 0;
        void changeID(int newID){ id = newID; }
        virtual void print(ostream &out);
};

问题是:没有析构函数,GCC 给了我一堆警告class has virtual functions but non-virtual destructor,但仍然可以编译并且我的程序运行良好。现在我想摆脱那些烦人的警告,所以我通过添加一个虚拟析构函数来满足编译器,结果是:它没有编译,错误:

undefined reference to `Media::~Media()`

将析构函数设为纯虚拟并不能解决问题。 那么到底出了什么问题呢?

【问题讨论】:

  • 你为什么首先想要一个纯虚拟构造函数?一个空的虚拟构造函数工作得很好(它相当于相同数量的非空白字符)
  • @LuchianGrigore 这不是我的意思。我为您改写一下:为什么您希望 Media 类是抽象的?
  • @drhirsch 通常是因为您想禁止创建它。也许 Media 类型的对象没有意义。
  • 如果有人只需要禁用“类具有虚函数但非虚析构函数”警告,那就是-Wno-non-virtual-dtor。不过,我并不建议将其作为这个问题的答案。

标签: c++


【解决方案1】:

你还需要定义虚拟析构函数,而不只是添加它。

//Media.h
class Media{
    //....
    virtual ~Media() = 0;
};

//Media.cpp
#include "Media.h"
//....
Media::~Media() {};

您收到警告的原因是所有将派生的类都应具有虚拟或受保护(信用@Steve)析构函数,否则通过指向基类的指针删除实例会导致未定义的行为。

注意您必须为析构函数提供定义,即使它们是纯虚拟的。

【讨论】:

  • “所有派生的类都应该有一个虚拟析构函数”——或者一个受保护的析构函数,这将防止外人通过错误的指针进行删除。这适用于外部人员不应该直接删除的情况(例如,因为对象是通过返回智能指针的某个工厂创建的)。
  • 不敢相信我必须实现一个空的析构函数。问题解决了,谢谢大家!
  • @crazyfffan:如果你的编译器足够新,你也可以说= default(C++11特性)
  • 仅仅定义空构造函数还不够吗:virtual ~Media() {}(Jerry Coffin 的回答)?
  • @pevik 如果可以使用= default,那么应该;用户提供一个特殊的成员函数,即使在实践中实现正是默认的做法,也可以阻止或阻碍其他功能。
【解决方案2】:

问题是:没有析构函数,GCC 给了我一堆警告“类具有虚函数但非虚析构函数”,但仍然可以编译并且 我的程序工作正常

这在现代 C++ 中是一个令人讨厌的警告,但在旧的对象风格 C++ 中它通常是正确的。

问题在于您的对象被破坏的方式。一个简单的测试:

#include <iostream>

class Base {};
class Derived: public Base { public: ~Derived() { std::cout << "Aargh\n"; } };

int main() {
  Base* b = new Derived();
  Derived* d = new Derived();

  delete d;
  delete b;
}

打印出来:

Aargh

是的,只有一次。

问题在于,当您对Base* 类型的变量调用delete 时,会调用Base::~Base() 方法。如果是virtual,则调用将动态分派到最终方法(基于动态类型),在本例中为Derived::~Derived(),但如果不是,则永远不会调用Derived::~Derived(),因此永远不会执行。

因此,如果您希望在基本类型上调用delete(或使用智能指针),那么您需要在其类定义中添加virtual ~Base() {}。这就是为什么 gcc 在您创建没有 virtual 析构函数的多态类时会发出警告的原因。


注意:时间变了,从那以后我在 Clang 中实现了-Wdelete-non-virtual-dtor,它也被复制到了 gcc 中。

-Wnon-virtual-dtor 对库编写者很有用(因为它在基类上发出警告),但可能有更高的误报率;另一方面,-Wdelete-non-virtual-dtor 会在调用站点触发,并且误报率要低得多(您通常可以通过使用 final 来消除该类的“多态”属性来解决此问题)。

【讨论】:

  • 但请注意,最不正式的是,大部分都是猜测。正式地,通过对基的指针/引用销毁派生对象需要一个虚拟 dtor——否则,行为只是未定义的。
【解决方案3】:

您注释掉的是析构函数的纯虚拟声明。这意味着必须在派生类中重写该函数才能实例化该类的对象。

你想要的只是将析构函数定义为虚函数:

virtual ~Media() {}

在 C++ 11 或更高版本中,通常最好将其定义为默认值,而不是使用空主体:

virtual ~Media() = default;

【讨论】:

    【解决方案4】:

    您应该实现虚拟析构函数,而不是使其成为纯虚拟的。

    查看this 类似问题(从虚拟析构函数错误的角度来看可能相同,而不是警告)以获取更多信息。

    编辑:更通用的解决方案,回复 LuchianGrigore 的评论(感谢您指出)

    您还可以将析构函数设为纯虚拟并按照上述问题中的说明实现。

    在你的类中使用虚拟析构函数应该是为了防止基类的实例化(即当你没有其他纯虚拟方法来使类抽象时)。

    【讨论】:

    • 纯虚拟析构函数有其用途。但即使声明为纯,仍然需要定义。
    • 是的,我的回答只是关于错误。我将在一个类中添加纯虚拟析构函数的使用以及在我的答案中实现它们的方法。 +1
    【解决方案5】:

    先取消注释,然后再取消注释 尝试在类声明后添加以下行

    Media::~Media(){}
    

    【讨论】:

    • 没有。在类声明之后添加这个将在链接期间产生多个定义。定义只能在一个翻译单元中。
    • 这仍然不正确。在类声明之后的头文件中添加它会导致错误。
    • @LuchianGrigore 你能告诉我如果我在 .h 文件中添加定义,编译器会给出什么样的错误?它在 Visual Studio 上运行良好
    • @Hiren 如果将它放在包含在多个 c++ 源代码中的头文件中,则会出现链接器错误,因为该定义将在每个 .cpp 文件中复制一次。
    猜你喜欢
    • 2012-04-13
    • 2011-08-12
    • 2012-09-21
    • 2017-05-31
    • 2016-08-14
    • 1970-01-01
    • 1970-01-01
    • 2013-07-06
    • 1970-01-01
    相关资源
    最近更新 更多