【问题标题】:GNU compiler warning "class has virtual functions but non-virtual destructor"GNU 编译器警告“类具有虚函数但非虚析构函数”
【发布时间】:2010-09-12 17:58:00
【问题描述】:

我在 C++ 中定义了一个接口,即一个只包含纯虚函数的类。

我想明确禁止接口的用户通过指向接口的指针删除对象,所以我为接口声明了一个受保护的非虚拟析构函数,类似于:

class ITest{
public:
    virtual void doSomething() = 0;

protected:
    ~ITest(){}
};

void someFunction(ITest * test){
    test->doSomething(); // ok
    // deleting object is not allowed
    // delete test; 
}

GNU 编译器给我一个警告说:

类“ITest”具有虚函数但非虚析构函数

一旦析构函数被保护了,它是虚拟的还是非虚拟的有什么区别?

您认为可以安全地忽略或消除此警告吗?

【问题讨论】:

    标签: c++ gcc


    【解决方案1】:

    此答案中的一些 cmets 与我之前给出的答案有关,这是错误的。

    受保护的析构函数意味着它只能从基类调用,不能通过删除。这意味着不能直接删除 ITest*,只有派生类可以。派生类可能需要一个虚拟析构函数。您的代码完全没有问题。

    但是,由于您不能在 GCC 中本地禁用警告,并且您已经有一个 vtable,因此您可以考虑将析构函数设为虚拟。程序最多需要 4 个字节(不是每个类实例)。由于您可能已经为派生类提供了一个虚拟 dtor,因此您可能会发现它没有任何成本。

    【讨论】:

    • 2 个原因。 (1) 您只需为在 C++ 中使用的内容付费。如果 dtor 是虚拟的,那么很多类都会有不必要的 vtable。 (2) 我认为某些框架(例如 CORBA)现在依赖于当前行为。
    • Richard 和 Airsource,我删除了引用上一个答案的 cmets。我现在对您的回答没有任何问题,并将我最​​初的 -1 更改为 +1...
    • 无论派生类析构函数是否是虚拟的,在此处标记基类的析构函数都不会产生任何运行时开销,因为它只会在从基类中多态删除时发挥作用。由于受保护的原因,这不可能发生......(除了可能删除它)
    • 当然没有 CPU 开销,除非该函数实际上是以涉及 vtable 查找的方式调用的。不过会有 4 个字节的开销,因为 vtable 会因为有一个额外的虚函数而大 4 个字节。
    • @AirsourceLtd 在 32 位系统上为 4 个字节,在 64 位系统上为 8 个字节……如果有一天我们拥有更大地址的系统,则更多字节。
    【解决方案2】:

    这或多或少是编译器中的一个错误。请注意,在最新版本的编译器中,不会抛出此警告(至少在 4.3 中不会)。在您的情况下,让析构函数受到保护且非虚拟是完全合法的。

    请参阅here,查看 Herb Sutter 撰写的关于该主题的优秀文章。来自文章:

    准则 #4:基类析构函数应该是公共的和虚拟的,或者是受保护的和非虚拟的。

    【讨论】:

    • 我相信这只是一个警告 - 上述情况是例外情况。一般来说,如果你有虚函数,你可能还应该有一个虚析构函数。
    • 我同意这是一个小错误,即在受保护的析构函数存在时警告不会被抑制。其他提到由派生类删除的答案是有效的,但与常规缺失的虚拟析构函数相比,它出现问题的可能性要小得多。
    • typo?:“让构造函数受到保护……”,我假设您的意思是“析构函数”。编辑?
    【解决方案3】:

    我个人的观点是,你会做正确的事情,编译器坏了。如果可能,我会禁用警告(在定义接口的文件中本地),

    我发现我经常使用这种模式(小“p”)。事实上,我发现我的接口拥有受保护的 dtor 比拥有公共的更常见。但是我不认为它实际上是一个常见的习语(它并没有被谈论那么多),我猜想当警告被添加到 GCC 时,尝试强制执行旧的 'dtor 必须是虚拟的,如果你有虚函数的规则。就我个人而言,我将该规则更新为“dtor 必须是虚拟的,如果你有虚拟功能并希望用户能够通过接口删除接口的实例,否则 dtor 应该受到保护且非虚拟”很久以前;)

    【讨论】:

      【解决方案4】:

      它是一个接口类,因此您不应该删除通过该接口实现该接口的对象是合理的。一个常见的情况是工厂创建的对象的接口应该返回工厂。 (让对象包含指向其工厂的指针可能会非常昂贵)。

      我同意 GCC 抱怨的观察结果。相反,它应该在您删除 ITest* 时发出警告。这才是真正的危险所在。

      【讨论】:

        【解决方案5】:

        如果您坚持这样做,请继续将-Wno-non-virtual-dtor 传递给 GCC。默认情况下似乎没有打开此警告,因此您必须使用-Wall-Weffc++ 启用它。但是,我认为这是一个有用的警告,因为在大多数情况下,这将是一个错误。

        【讨论】:

          【解决方案6】:

          如果您在ITest 的方法之一中有代码尝试delete 本身(一个坏主意,但合法),派生类的析构函数将不会被调用。即使您从不打算通过基类指针删除派生实例,您仍应将析构函数设为虚拟。

          【讨论】:

          • 不,他不应该。他定义的接口契约是接口的用户不能删除实现它的对象的实例。对于通过工厂或通过引用计数分配和清理的对象来说,这是一种非常常见的模式......(-1)
          【解决方案7】:

          如果析构函数是虚拟的,它确保基类析构函数在清理之前也被调用,否则代码可能会导致一些泄漏。所以你应该确保程序没有这样的警告(最好完全没有警告)。

          【讨论】:

            猜你喜欢
            • 2011-08-15
            • 2012-02-04
            • 2012-09-21
            • 1970-01-01
            • 2011-11-16
            • 2011-09-16
            • 2012-04-18
            • 1970-01-01
            • 2015-09-02
            相关资源
            最近更新 更多