【问题标题】:Destructors in non-polymorphic base classes非多态基类中的析构函数
【发布时间】:2014-05-17 20:19:01
【问题描述】:

考虑这段代码:

class A {
  public:
    void fun() {}
};

class B : public A {
  public:
    void fun() {}
};

int main()
{
    A *p = new B;

    delete p;
}

类 A 和 B 不是多态的,并且两个类都没有声明析构函数。如果我用g++ -Wall 编译这段代码,GCC 编译器会愉快地编译代码。

但是,如果我在 A 中将 virtual 添加到 void fun(),编译器会发出以下警告:“删除具有非虚拟析构函数的多态类类型‘A’的对象可能会导致未定义的行为”。

我非常清楚使用非虚拟析构函数的危险。但是上面的代码让我想知道两件事:

  1. 当我根本不使用析构函数时,为什么我需要在基类中编写一个空的虚拟析构函数?
  2. 如果基类不包含其他虚函数,为什么不需要空的虚析构函数?

编辑

看来我需要澄清困扰我的事情:

上面的代码没有声明任何析构函数。

如果我声明一个虚函数,编译器会抱怨缺少虚析构函数。我的结论:如果类是多态的,如果delete p要正常工作,我需要写一个虚析构函数。

但是如果我没有声明虚函数(如上面的初始示例),编译器不会抱怨缺少虚析构函数。我的结论:如果类不是多态的,我不需要写虚析构函数,delete p 无论如何都会正常工作。

但最后一个结论在我看来直觉上是错误的。这是错的吗?编译器应该在这两种情况下都抱怨吗?

【问题讨论】:

  • 这两个问题都断言完全错误的前提,这使得回答起来很尴尬。手头的代码应该简单地写成B *p = new B;,一切都会好起来的。
  • 我在你的代码中没有看到 empty virtual destructor
  • @oz1cz - Why do I need to write an empty virtual destructor in the base class when I'm not using destructors at all? 所有类都有析构函数。
  • @Kerrek: A *p = new B; delete p; 是完全合法的。问题是它是否适用于非多态类?如果类是多态的,编译器会发出警告,但如果它是非多态的则不会。为什么?
  • @Nawaz:你当然不知道。我没有写任何东西,编译器抱怨我没有写任何东西。但是,编译器只在类是多态的时候才会抱怨。为什么?

标签: c++ polymorphism destructor


【解决方案1】:

跟进 PaulMcKenzie 和 KerrekSB 的 cmets,以下是原帖中两个问题的答案:

  1. 这个类总是有一个析构函数,即使程序员没有明确地写一个析构函数。需要声明一个空的虚析构函数,以防止系统自动生成一个非虚析构函数。
  2. 在提供的示例代码中,您确实需要一个虚拟析构函数,即使类中没有其他虚拟函数也是如此。 GCC 在这种情况下没有抱怨的事实可能是编译器中的一个错误(或至少是一个缺点)。

这方面的背景可以在 C++11 标准的 §5.3.5 中找到,其中说:“如果要删除的对象的静态类型与其动态类型不同,则静态类型应为基类要删除的对象的动态类型的类,静态类型应具有虚拟析构函数,否则行为未定义。" (斜体是我的。)

【讨论】:

    【解决方案2】:

    您正在进行向上转换,换句话说:B 类的多态使用。如果 A 类没有虚拟成员,则编译器不会为 A 类生成 VTABLE,并且不需要虚拟析构函数(请注意如果不使用多态性,您的向上转型毫无意义)。而如果类 A 声明了虚拟成员,则编译器会生成一个 VTABLE,在这种情况下,您应该提供一个虚拟析构函数。

    【讨论】:

    • 那么你是说如果 A 和 B 不是多态的,A *p = new B; delete p; 是安全的吗?
    • 对我来说是的,在这种情况下,您仅使用 B 继承的 A 实现。B 的部分不是多态的,因此对 A 不可见。但是如果您在 B 中实现析构函数向上转型是不安全的。
    【解决方案3】:

    如果您想要多态行为,您需要定义至少一个虚函数,以便编译器为您的类生成 v-table。

    因为 C++ 类包含两个用于每个对象的特殊函数(构造函数和析构函数),所以将析构函数设为虚拟是一个不错的选择。

    1. 当您编写delete p 时,您实际上调用了与指针p 关联的对象的析构函数。如果你没有将析构函数声明为虚拟的,你就会得到容易出错的代码。

    2. 在您将至少一个成员函数声明为虚拟编译器之前,没想到您打算将您的类用作多态。在 C++ 哲学中:“您不应该为永远不会使用的功能付费”。例如。在简单的情况下,析构函数不应该是虚拟的。

    【讨论】:

    • 所以如果我理解正确的话,系统总是会为我生成一个析构函数。因此,我需要强制析构函数是虚拟的,以确保在我说delete p 时调用正确的析构函数。这是有道理的,但它没有解释如果类不是多态的,delete p 如何正确工作(即调用正确的构造函数)。或者你真的是在说,“delete p 没有虚拟析构函数就无法正常工作,但编译器只有在存在其他虚拟函数时才会发出警告。”?
    • 如果你写delete p时析构函数不是虚拟的,那么你的变量的静态类型(例如'A')而不是动态的('B')应该被称为析构函数。
    猜你喜欢
    • 2014-02-23
    • 2013-11-03
    • 2015-06-04
    • 1970-01-01
    • 2013-11-01
    • 2016-02-01
    • 2018-06-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多