【问题标题】:Derived class with non-virtual destructor具有非虚拟析构函数的派生类
【发布时间】:2011-11-16 06:16:51
【问题描述】:

在任何情况下派生类具有非virtual 析构函数是合法的吗?非virtual 析构函数表示不应将类用作基类。派生类的非virtual 析构函数是否会像Java final 修饰符的弱形式一样?

我对派生类的基类有virtual析构函数的情况特别感兴趣。

【问题讨论】:

标签: c++


【解决方案1】:

是的,不和不。

虚拟析构函数与类是基类还是派生类的能力无关。两者都是合法的。

但是,将析构函数设为虚拟是有某些原因的。见这里:http://en.wikipedia.org/wiki/Virtual_destructor#Virtual_destructors。这使得一个类,除其他外,有一个虚拟表,如果它还没有的话。但是,C++ 不需要虚拟表来进行继承。

【讨论】:

【解决方案2】:

在任何情况下它是合法的派生 类有一个非虚拟析构函数?

是的。

非虚拟析构函数表示不应将类用作 一个基类。

不是真的;非虚拟析构函数表示通过base 指针删除derived 的实例将不起作用。例如:

class Base {};
class Derived : public Base {};

Base* b = new Derived;
delete b; // Does not call Derived's destructor!

如果你不按上述方式做delete,那就没问题了。但如果是这种情况,那么您可能会使用组合而不是继承。

派生类的非虚拟析构函数会像一个 Java final 修饰符的弱形式?

不,因为virtual-ness 会传播到派生类。

class Base
{
public:
    virtual ~Base() {}
    virtual void Foo() {};
};

class Derived : public Base
{
public:
    ~Derived() {}  // Will also be virtual
    void Foo() {}; // Will also be virtual
};

在 C++03 或更早版本中没有内置的语言机制来防止子类 (*)。反正这不是什么大问题,因为你应该总是prefer composition over inheritance。也就是说,当“is-a”关系比真正的“has-a”关系更有意义时,使用继承。

(*) 'final' 修饰符是在 C++11 中引入的

【讨论】:

  • "virtual-ness 传播到派生类。"有吗? [查阅标准]。确实如此,12.4.7:“如果一个类有一个带有虚拟析构函数的基类,那么它的析构函数(无论是用户声明的还是隐式声明的)都是虚拟的。
  • 在相关的说明中,如果您有一个非虚拟基类,并且您的派生类中有一些虚拟方法,那么Base *b = new Derived(); delete b; 将是未定义的行为并可能使您的程序崩溃。它看起来很安全,但事实并非如此。 (这是因为b 不会指向Derived 对象的“开始” - 它将被vtable 所需的空间所抵消。然后,delete 将不会在完全相同的地址上运行new,因此它不是一个有效的释放地址。如果你要在任何地方拥有虚拟方法,那么在基址中放置一个虚拟 d'tor。
  • @AaronMcDaid 它将被 vtable 所需的空间抵消,它是 vptr 而不是 vtable,vtable 是每个类和 vptr是每个对象。它是对象中包含的 vptr。
【解决方案3】:

一个非虚拟析构函数是完全可以的,只要你不想在删除对象时将它用作派生类的基指针。

如果你以多态的方式派生类,用基指针传递和存储它,然后删除它,那么答案是否定的,使用虚拟析构函数。

【讨论】:

  • 我对以下内容的评论:只要您不想使用其超类类型之一的指针删除对象,非虚拟析构函数就完全可以了。 ...即使析构函数是虚拟的,如果超类析构函数不是虚拟的,使用超类类型的指针删除对象也会调用未定义的行为。
  • 那仍然不正确。您需要编辑旧句子。
【解决方案4】:

如果您永远不会对指向派生类对象的基类指针调用 delete,那么拥有一个带有非虚拟析构函数的基类是完全有效的。

Follow Herb Sutter's Advice:

指南#:只有在派生类需要调用虚函数的基实现时,才使虚函数受到保护。 仅针对析构函数的特殊情况:

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


也许您的问题实际上是:
如果基类 Destructor 是虚拟的,Derived 类中的 Destructor 是否需要是虚拟的?

答案是否。
如果基类析构函数是虚拟的,那么派生类析构函数已经是隐式虚拟的,你不需要显式指定它为虚拟。

【讨论】:

  • "如果基类析构函数是虚拟的,派生类中的析构函数是否需要是虚拟的?"是的,这确实是我的问题。
  • 如果派生类中没有分配数据(堆栈、堆)(即基对象和派生对象的大小相等),您仍然可以使用公共非虚拟基析构函数。这可以是需要显式强制转换的“typedef”的解决方案:向量基类(一些不可修改的 API),具有点、法线和方向子类以及显式构造函数。
【解决方案5】:

取决于你上课的目的。有时最好让你的析构函数受保护,但不是虚拟的——这基本上是说:“你不应该通过基类型指针删除派生类的对象”

【讨论】:

    【解决方案6】:

    是的,有:

    void dothis(Base const&);
    
    void foo() {
      Derived d;
      tothis(d);
    }
    

    这里的类是多态使用的,但是delete没有被调用,这样就可以了。

    另一个例子是:

    std::shared_ptr<Base> create() { return std::shared_ptr<Base>(new Derived); }
    

    因为shared_ptr 能够使用非多态delete(通过类型擦除)。

    我在 Clang 中实现了一个警告,专门用于检测对具有非虚拟析构函数的多态非最终类的 delete 调用,因此如果您使用 clang -Wdelete-non-virtual-dtor,它会针对这种情况发出专门的警告。

    【讨论】:

      【解决方案7】:

      如果你的派生类没有向基类添加任何数据成员,并且有一个空的析构函数体,那么析构函数是否是虚拟的都没有关系——派生析构函数所做的只是调用基类反正一个。不建议这样做,因为有人很容易在不了解这些限制的情况下修改课程。

      如果您从不尝试通过指向基类的指针来删除对象,那么您将是安全的。这是另一条难以执行的规则,应谨慎使用。

      有时你对基类没有任何控制权,你不得不从它派生,即使析构函数不是虚拟的。

      最后,在基类中有一个非虚拟析构函数不会对派生类施加任何编译器强制执行的限制,所以我认为它根本不像 Java 的 final。

      【讨论】:

      • 听到听到。不久前,我不得不从 std::vector 派生,它有一个非虚拟析构函数,以保持向后兼容性。新类没有添加数据成员,也没有自己的析构函数,所以一切都很好;并保证没问题。根据我对 C++ 标准的理解,这种行为是标准的(在这种特殊情况下),永远不会有未定义的行为。
      【解决方案8】:

      你的问题不是很清楚。如果基类有一个虚 析构函数,派生类将有一个,无论如何。不可能 声明后关闭虚拟性。

      在某些情况下,从 没有虚拟析构函数的类。基地的原因 类析构函数应该是虚拟的,这样你就可以通过一个 指向基类的指针。如果派生是私有的,则您没有 担心这一点,因为您的 Derived* 不会转换为 Base*。 否则,我已经看到了如果基类的建议 析构函数不是虚拟的,它应该受到保护;这可以防止一个 未定义行为的情况(通过指向基的指针删除) 可能发生。然而,在实践中,许多基类(例如 std::iterator&lt;&gt;) 具有这样的语义,以至于它甚至不会发生 任何人都可以创建指向它们的指针;更不用说通过这样的删除 指针。因此,添加保护可能会付出更多的努力而不值得。

      【讨论】:

      • “这可以防止可能发生的未定义行为(通过指向基的指针删除)的一种情况”-您可以拥有一个本身有用的基,尽管它是非虚拟的,对吗?
      【解决方案9】:

      解决最新编辑:

      编辑:我对派生类的基类具有虚拟析构函数的情况特别感兴趣。

      在这种情况下,派生类的析构函数是虚拟的,无论是否添加virtual关键字:

      struct base {
         virtual ~base() {}       // destructor is virtual
      };
      struct derived : base {
         ~derived() {}            // destructor is also virtual, because it is virtual in base
      };
      

      这不仅限于析构函数,如果在类型层次结构中的任何位置函数成员被声明为虚拟的,则同一函数的所有覆盖(不是重载)都将是虚拟的,无论它们是否声明为虚拟。析构函数的特定位是 ~derived() overrides virtual ~base() 即使成员的 name 不同——这是析构函数的唯一特性。

      【讨论】:

        【解决方案10】:

        派生类的非虚拟析构函数会像 Java final 修饰符的弱形式吗?

        一点也不。这是我在 C++ 中防止子类的建议(如 Java 中的 final 修饰符);使析构函数在类中私有。然后你可以防止从中创建子类

        【讨论】:

          【解决方案11】:

          您可能不想在基类中创建虚拟析构函数?在这种情况下没有析构函数。如果您使用指向基类的指针并在父级编译器中创建非虚拟析构函数,则会自动生成此警告!如果你想创建最终的父类,你可以阻止它。但最好将其标记为final like:

          class Base{
          public:
              //No virtual destructor defined
              virtual void Foo() {};
          };
          
          class Derived final : public Base{
          public:
              ~Derived() {}  // define some non-virtual destructor
              void Foo() {}; // Will also be virtual
          };
          

          在这种情况下,编译器知道您想要什么,并且不会生成任何警告。 使用空虚拟基析构函数的决定不太好但可以接受。你不需要设置属性final。但这不是你想要的。你也不需要定义空的虚拟基方法 Foo 最好的是:

          class Base{
          public:
            //No virtual destructor defined
            virtual void Foo() = 0; // abstract method
          };
          class Derived final : public Base{
          public:
            ~Derived() {}  // define some non-virtual destructor
            void Foo() {}; // Will also be virtual
          };
          

          这是编译器的完全清晰的代码。不会生成警告,也不会使用备用代码。

          【讨论】:

            猜你喜欢
            • 2013-11-01
            • 2021-03-20
            • 2020-10-29
            • 2012-09-21
            • 2015-09-02
            • 2021-08-22
            • 2015-04-16
            • 2016-08-13
            • 2012-12-19
            相关资源
            最近更新 更多