具有非虚拟析构函数的派生类

Derived class with non-virtual destructor

提问人:Raedwald 提问时间:9/13/2011 最后编辑:Raedwald 更新时间:8/26/2023 访问量:91021

问:

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

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

C++

答:

0赞 Pavel Radzivilovsky 9/13/2011 #1

是的,否和否。

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

但是,使析构函数虚拟化是有某些原因的。请参阅此处:http://en.wikipedia.org/wiki/Virtual_destructor#Virtual_destructors。这使得一个类(除其他外)有一个虚拟表(如果还没有虚拟表)。但是,C++ 不需要虚拟表来执行继承。

评论

0赞 Martin York 9/13/2011
维基百科非常适合作为研究的起点,它不应用作参考来源。wikihow.com/Cite-a-Wikipedia-Article-in-MLA-Format
111赞 In silico 9/13/2011 #2

是否有任何情况可以使派生 类有一个非虚拟析构函数?

是的。

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

没有;非虚拟析构函数表示删除 via a 指针的实例将不起作用。例如:derivedbase

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

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

如果你不以上述方式做,那就没问题了。但如果是这样的话,那么你可能会使用组合而不是继承。delete

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

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

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++11 标准的摘录,正式定义了这一点:

[...]如果一个类具有具有虚拟析构函数的基类,则其析构函数(无论是用户声明的还是隐式声明的)是虚拟的。

- [类.dtor] 第 9 页

C++03 或更早版本中没有内置的语言机制来防止子类 (*)。无论如何,这并不是什么大问题,因为您应该始终更喜欢组合而不是继承。也就是说,当“is-a”关系比真正的“has-a”关系更有意义时,使用继承。

(*)C++ 11 中引入了“final”修饰符

评论

7赞 Aaron McDaid 11/5/2013
与此相关的是,如果您有一个非虚拟基类,并且派生类中有一些虚拟方法,那么将出现未定义的行为,并可能使程序崩溃。它看起来很安全,但事实并非如此。 (这是因为它不会指向对象的“开始” - 它将被 vtable 所需的空间所抵消。然后,它不会在与 完全相同的地址上运行,因此它不是 free 的有效地址。如果您要在任何地方使用虚拟方法,请在底座中放置一个虚拟 d'tor。Base *b = new Derived(); delete b;bDeriveddeletenew
3赞 Gab是好人 9/10/2019
@AaronMcDaid它将被 vtable 所需的空间偏移,它是 VPTR 而不是 VTABLE,VTABLE 是每个类,VPTR 是每个对象。它是包含在对象中的 vptr。
1赞 Karoly Horvath 9/13/2011 #3

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

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

评论

0赞 Nawaz 9/13/2011
我的评论 : 非虚拟析构函数是完全可以的,只要您不想删除带有其超类类型之一的指针的对象。即使析构函数是虚拟的,如果超类析构函数不是虚拟的,则使用超类类型的指针删除对象也会调用未定义的行为。
0赞 Nawaz 9/13/2011
这仍然是不正确的。您需要编辑旧句子。
38赞 Alok Save 9/13/2011 #4

如果从不对指向派生类对象的基类指针调用 delete,那么具有非虚拟析构函数的基类是完全有效的。

遵循 Herb Sutter 的建议

准则#:仅当派生类需要调用虚函数的基本实现时,才使虚函数受到保护。 仅适用于析构函数的特殊情况:

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


也许您的问题实际上是:
如果基类析构函数是虚拟的,那么派生类中的析构函数是否需要是虚拟的?

答案是否定的。如果基类析构函数是虚拟的,则派生类析构函数已经是隐式虚拟的,您不需要显式地将其指定为虚拟。

评论

2赞 Raedwald 9/13/2011
“如果基类析构函数是虚拟的,那么派生类中的析构函数是否需要是虚拟的?”是的,这确实是我的问题。
0赞 Matthias 3/26/2017
如果派生类中没有分配任何数据(堆栈、堆)(即基对象和派生对象的大小相等),您仍然可以使用公共非虚拟基析构函数。这可以是需要显式转换的“typedefs”的解决方案:一个向量基类(一些不可修改的 API),具有具有显式构造函数的点、法线和方向子类。
4赞 Nemanja Trifunovic 9/13/2011 #5

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

1赞 Matthieu M. 9/13/2011 #6

是的,有:

void dothis(Base const&);

void foo() {
  Derived d;
  tothis(d);
}

这里该类是多态使用的,但未被调用,因此很好。delete

另一个例子是:

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

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

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

2赞 Mark Ransom 9/13/2011 #7

如果派生类没有向基类添加任何数据成员,并且析构函数体为空,则析构函数是否为虚拟并不重要 - 派生的析构函数将要做的就是调用基类。不建议这样做,因为有人在不知道这些限制的情况下很容易出现并修改类。

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

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

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

评论

0赞 Graham Asher 6/12/2019
听,听。不久前,我不得不从 std::vector 派生,它有一个非虚拟析构函数,以保持向后兼容性。新类没有添加任何数据成员,也没有自己的析构函数,所以一切都很好;并且保证没事。根据我对C++标准的理解,这种行为是标准的(在这种特殊情况下),永远不会有未定义的行为。
5赞 James Kanze 9/13/2011 #8

你的问题不是很清楚。如果基类具有虚拟 析构函数,无论如何,派生类都会有一个。没办法 在声明虚拟性后将其关闭。

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

评论

0赞 pooya13 2/10/2021
“这可以防止可能发生的一种未定义行为(通过指向碱基的指针删除)的情况” -- 你可以有一个本身有用的基础,并且是非虚拟的,对吧?
16赞 David Rodríguez - dribeas 9/14/2011 #9

解决最新编辑:

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

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

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

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

0赞 SRF 11/18/2013 #10

拥有派生类的非虚拟析构函数会像 Java 最终修饰符的弱形式一样吗?

一点也不。这是我的建议,以防止C++中的子类(如Java中的最终修饰符);在类中将析构函数设为私有。然后,您可以阻止从中创建子类

0赞 tolya 10/24/2016 #11

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

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
};

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