为什么要在 C++ 中为抽象类声明虚拟析构函数?

发布于 2024-07-09 01:29:16 字数 84 浏览 10 评论 0原文

我知道在 C++ 中为基类声明虚拟析构函数是一个很好的做法,但是即使对于充当接口的抽象类,声明虚拟析构函数总是很重要吗? 请提供一些原因并举例说明原因。

I know it is a good practice to declare virtual destructors for base classes in C++, but is it always important to declare virtual destructors even for abstract classes that function as interfaces? Please provide some reasons and examples why.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

海拔太高太耀眼 2024-07-16 01:29:16

对于界面来说更重要。 您的类的任何用户都可能持有指向接口的指针,而不是指向具体实现的指针。 当他们删除它时,如果析构函数是非虚拟的,那么会发生什么取决于编译器。

例如

class Interface
{
   virtual void doSomething() = 0;
};

class Derived : public Interface
{
   Derived();
   ~Derived() 
   {
      // Do some important cleanup...
   }
};

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived in
   // which case you have a memory leak. Or it may crash
   delete p;
}

It's even more important for an interface. Any user of your class will probably hold a pointer to the interface, not a pointer to the concrete implementation. When they come to delete it, if the destructor is non-virtual, then what happens is up to the compiler.

For example

class Interface
{
   virtual void doSomething() = 0;
};

class Derived : public Interface
{
   Derived();
   ~Derived() 
   {
      // Do some important cleanup...
   }
};

void myFunc(void)
{
   Interface* p = new Derived();
   // The behaviour of the next line is undefined. It probably 
   // calls Interface::~Interface, not Derived::~Derived in
   // which case you have a memory leak. Or it may crash
   delete p;
}
终陌 2024-07-16 01:29:16

您的问题的答案经常是,但并非总是如此。 如果你的抽象类禁止客户端在指向它的指针上调用delete(或者它的文档中这么说),那么你可以不声明虚拟析构函数。

您可以通过保护其析构函数来禁止客户端对指向它的指针调用删除。 像这样工作,省略虚拟析构函数是完全安全且合理的。

您最终将没有虚拟方法表,并最终向您的客户表明您打算通过指向它的指针使其不可删除,因此您确实有理由在这些情况下不将其声明为虚拟的。

[请参阅本文中的第 4 项:http://www.gotw.ca/publications/ mill18.htm]

The answer to your question is often, but not always. If your abstract class forbids clients to call delete on a pointer to it (or if it says so in its documentation), you are free to not declare a virtual destructor.

You can forbid clients to call delete on a pointer to it by making its destructor protected. Working like this, it is perfectly safe and reasonable to omit a virtual destructor.

You will eventually end up with no virtual method table, and end up signalling your clients your intention on making it non-deleteable through a pointer to it, so you have indeed reason not to declare it virtual in those cases.

[See item 4 in this article: http://www.gotw.ca/publications/mill18.htm]

绅士风度i 2024-07-16 01:29:16

我决定做一些研究并尝试总结您的答案。 以下问题将帮助您决定需要哪种类型的析构函数:

  1. 您的类是否打算用作基类?
    • 否:声明公共非虚拟析构函数以避免类 * 的每个对象上的 v 指针。
    • 是:阅读下一个问题。
  2. 你的基类是抽象的吗? (即任何虚拟纯方法?)
    • 否:尝试通过重新设计类层次结构来使基类抽象
    • 是:阅读下一个问题。
  3. 您想允许通过基指针进行多态删除吗?
    • 否:声明受保护的虚拟析构函数以防止不必要的使用。
    • 是:声明公共虚拟析构函数(在本例中没有开销)。

我希望这有帮助。

* 需要注意的是,C++ 中无法将类标记为 Final(即不可子类化),因此,如果您决定将析构函数声明为非虚拟且公共的,请记住明确警告你的程序员同事不要从你的类派生。

参考文献:

I decided to do some research and try to summarise your answers. The following questions will help you to decide what kind of destructor you need:

  1. Is your class intended to be used as a base class?
    • No: Declare public non-virtual destructor to avoid v-pointer on each object of the class *.
    • Yes: Read next question.
  2. Is your base class abstract? (i.e. any virtual pure methods?)
    • No: Try to make your base class abstract by redesigning your class hierarchy
    • Yes: Read next question.
  3. Do you want to allow polymorphic deletion through a base pointer?
    • No: Declare protected virtual destructor to prevent the unwanted usage.
    • Yes: Declare public virtual destructor (no overhead in this case).

I hope this helps.

* It is important to note that there is no way in C++ to mark a class as final (i.e. non subclassable), so in the case that you decide to declare your destructor non-virtual and public, remember to explicitly warn your fellow programmers against deriving from your class.

References:

满天都是小星星 2024-07-16 01:29:16

是的,它总是很重要。 派生类可以分配内存或保存对其他资源的引用,这些资源在对象被销毁时需要清理。 如果您没有为接口/抽象类提供虚拟析构函数,那么每次通过基类句柄删除派生类实例时,都不会调用派生类的析构函数。

因此,您正在打开内存泄漏的可能性

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted

Yes it is always important. Derived classes may allocate memory or hold reference to other resources that will need to be cleaned up when the object is destroyed. If you do not give your interfaces/abstract classes virtual destructors, then every time you delete a derived class instance via a base class handle your derived class' destructor will not be called.

Hence, you're opening up the potential for memory leaks

class IFoo
{
  public:
    virtual void DoFoo() = 0;
};

class Bar : public IFoo
{
  char* dooby = NULL;
  public:
    virtual void DoFoo() { dooby = new char[10]; }
    void ~Bar() { delete [] dooby; }
};

IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
子栖 2024-07-16 01:29:16

这并不总是必需的,但我发现这是一个很好的做法。 它的作用是允许通过基类型的指针安全地删除派生对象。

例如:

Base *p = new Derived;
// use p as you see fit
delete p;

如果 Base 没有虚拟析构函数,则格式错误,因为它将尝试删除对象,就好像它是 Base * 一样。

It is not always required, but I find it to be good practice. What it does, is it allows a derived object to be safely deleted through a pointer of a base type.

So for example:

Base *p = new Derived;
// use p as you see fit
delete p;

is ill-formed if Base doesn't have a virtual destructor, because it will attempt to delete the object as if it were a Base *.

落在眉间の轻吻 2024-07-16 01:29:16

这不仅是好的做法。 这是任何类层次结构的第一条规则。

  1. C++ 层次结构的最基类必须有一个虚拟析构函数

现在就来说说为什么。 以典型的动物等级制度为例。 虚拟析构函数像任何其他方法调用一样经历虚拟分派。 以下面的例子为例。

Animal* pAnimal = GetAnimal();
delete pAnimal;

假设 Animal 是一个抽象类。 C++ 知道要调用的正确析构函数的唯一方法是通过虚拟方法分派。 如果析构函数不是虚拟的,那么它将简单地调用 Animal 的析构函数,并且不会销毁派生类中的任何对象。

在基类中将析构函数设置为虚拟的原因是它只是从派生类中删除选择。 它们的析构函数默认变为虚拟的。

It's not only good practice. It is rule #1 for any class hierarchy.

  1. The base most class of a hierarchy in C++ must have a virtual destructor

Now for the Why. Take the typical animal hierarchy. Virtual destructors go through virtual dispatch just as any other method call. Take the following example.

Animal* pAnimal = GetAnimal();
delete pAnimal;

Assume that Animal is an abstract class. The only way that C++ knows the proper destructor to call is via virtual method dispatch. If the destructor is not virtual then it will simply call Animal's destructor and not destroy any objects in derived classes.

The reason for making the destructor virtual in the base class is that it simply removes the choice from derived classes. Their destructor becomes virtual by default.

烟凡古楼 2024-07-16 01:29:16

答案很简单,你需要它是虚拟的,否则基类就不是一个完整的多态类。

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

您更喜欢上述删除,但如果基类的析构函数不是虚拟的,则仅调用基类的析构函数,并且派生类中的所有数据将保持不变。

The answer is simple, you need it to be virtual otherwise the base class would not be a complete polymorphic class.

    Base *ptr = new Derived();
    delete ptr; // Here the call order of destructors: first Derived then Base.

You would prefer the above deletion, but if the base class's destructor is not virtual, only the base class's destructor will be called and all data in derived class will remain undeleted.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文