为什么要在C+;中为抽象类声明虚拟析构函数+;? < >我知道在C++中声明基类的虚析构函数是一个好的做法,但声明“代码>虚拟析构函数是否总是重要的,即使对于抽象的类,它也可以作为接口吗?请提供一些原因和示例。

为什么要在C+;中为抽象类声明虚拟析构函数+;? < >我知道在C++中声明基类的虚析构函数是一个好的做法,但声明“代码>虚拟析构函数是否总是重要的,即使对于抽象的类,它也可以作为接口吗?请提供一些原因和示例。,c++,inheritance,virtual-destructor,C++,Inheritance,Virtual Destructor,对于界面来说,这更为重要。类的任何用户都可能持有指向接口的指针,而不是指向具体实现的指针。当他们删除它时,如果析构函数是非虚拟的,他们将调用接口的析构函数(或者编译器提供的默认值,如果您没有指定),而不是派生类的析构函数。即时内存泄漏 比如说 class Interface { virtual void doSomething() = 0; }; class Derived : public Interface { Derived(); ~Derived() {

对于界面来说,这更为重要。类的任何用户都可能持有指向接口的指针,而不是指向具体实现的指针。当他们删除它时,如果析构函数是非虚拟的,他们将调用接口的析构函数(或者编译器提供的默认值,如果您没有指定),而不是派生类的析构函数。即时内存泄漏

比如说

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

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

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

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

例如:

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

如果
Base
没有虚拟析构函数,则会出现格式错误,因为它会尝试删除对象,就像它是
Base*

一样。这不仅是一种好做法。这是任何类层次结构的规则#1

  • C++中的最底层的层次结构必须具有虚拟析构函数
  • 现在说说为什么。以典型的动物等级制度为例。虚拟析构函数与任何其他方法调用一样进行虚拟分派。以下面的例子为例

    Animal* pAnimal = GetAnimal();
    delete pAnimal;
    
    假设动物是一个抽象类。C++知道正确的析构函数调用的唯一方法是通过虚拟方法调度。如果析构函数不是虚拟的,那么它将只调用Animal的析构函数,而不销毁派生类中的任何对象


    在基类中使析构函数为虚拟的原因是,它只是从派生类中删除选择。默认情况下,它们的析构函数变为虚拟

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

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

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


    [见本文第4项:

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

  • 您的类是否打算用作基类?
    • 否:声明公共非虚拟析构函数以避免类*的每个对象上的v指针
    • 是:读下一个问题
  • 基类是抽象的吗?(即任何虚拟纯方法?
    • 否:尝试通过重新设计类层次结构使基类抽象化
    • 是:读下一个问题
  • 是否允许通过基指针进行多态删除?
    • 否:声明受保护的虚拟析构函数以防止不必要的使用
    • 是:声明公共虚拟析构函数(在这种情况下没有开销)
  • 我希望这有帮助

    重要的是要注意C++中没有方法将一个类标记为最终的(即非子类),因此在决定声明析构函数非虚的和公共的情况下,记住要明确地警告你的程序员不要从你的类中派生。 参考资料:

    • “S. Meyers。更有效的C++,项目33 Addison Wesley,1996。”
    • 当然,这个问题的答案

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

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

    您希望执行上述删除操作,但如果基类的析构函数不是虚拟的,则只调用基类的析构函数,并且派生类中的所有数据都将保持未删除状态。

    True,事实上,在该示例中,它可能不仅仅是内存泄漏,还可能崩溃:-/使答案正常工作的关键是“不要求删除的。“通常,如果你有一个抽象基类被设计成一个接口,delete将在接口类上被调用。正如上面John指出的,你的建议是非常危险的。您所依赖的假设是,接口的客户端永远不会破坏只知道基类型的对象。如果抽象类是非虚拟的,唯一可以保证的方法是保护抽象类的dtor。Michel,我说过:)“如果这样做,就保护析构函数。如果这样做,客户端将无法使用指向该接口的指针进行删除。”事实上,它并不依赖于客户端,但它必须强制执行,告诉客户“你做不到……”。我看不出有什么危险。我现在纠正了我回答中糟糕的措辞。它现在明确声明它不依赖于客户机。事实上,我认为很明显,依靠客户做一些事情是行不通的。感谢:)+1提到受保护的析构函数,这是在删除指向基类的指针时意外调用错误析构函数的另一个“出路”。是否要将boost::shared_指针p(新派生)修复为boost::shared_指针