当前位置:  开发笔记 > 编程语言 > 正文

何时使用虚拟析构函数?

如何解决《何时使用虚拟析构函数?》经验,为你挑选了9个好方法。

我对大多数OO理论有了深刻的理解,但让我困惑的一件事是虚拟析构函数.

我认为无论什么以及链中的每个对象,析构函数总是会被调用.

你什么时候打算让它们成为虚拟的?为什么?



1> Luc Touraill..:

当您可能通过指向基类的指针删除派生类的实例时,虚拟析构函数很有用:

class Base 
{
    // some virtual methods
};

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

在这里,您会注意到我没有声明Base的析构函数virtual.现在,我们来看看以下代码段:

Base *b = new Derived();
// use b
delete b; // Here's the problem!

由于Base的析构函数不是virtual并且bBase*指向Derived对象,因此delete b具有未定义的行为:

[In delete b],如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类,静态类型应具有虚拟析构函数或者行为未定义.

在大多数实现中,对析构函数的调用将像任何非虚拟代码一样被解析,这意味着将调用基类的析构函数,但不会调用派生类的析构函数,从而导致资源泄漏.

总而言之,virtual当它们被多态地操纵时,总是使基类的析构函数成为可能.

如果要防止通过基类指针删除实例,可以使基类析构函数受保护且非虚拟; 通过这样做,编译器将不允许您调用delete基类指针.

您可以在Herb Sutter的这篇文章中了解有关虚拟性和虚拟基类析构函数的更多信息.


这可以解释为什么我使用我之前制造的工厂发生了大量泄漏.一切都有意义.谢谢
来自Herb Sutter的文章:"准则#4:基类析构函数应该是公共的和虚拟的,或者是受保护的和非虚拟的."
嗯,这是一个糟糕的例子,因为没有数据成员.如果`Base`和`Derived`有_all_自动存储变量怎么办?即在析构函数中没有"特殊"或额外的自定义代码.那么可以放弃编写任何析构函数吗?或者派生类_still_会有内存泄漏吗?
同样在文章中-“如果您在没有虚拟析构函数的情况下进行多态删除,则会引起可怕的“未定义行为”的幽灵,我个人甚至不愿在中等光线的胡同中遇到这种幽灵,非常感谢。大声笑
[等等,这将是不确定的行为](http://stackoverflow.com/questions/2100644/will-using-delete-with-a-base-class-pointer-cause-a-memory-leak)

2> Tunvir Rahma..:

虚拟构造函数是不可能的,但虚拟析构函数是可能的.让我们实验......

#include 

using namespace std;

class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

上面的代码输出如下:

Base Constructor Called
Derived constructor called
Base Destructor called

派生对象的构造遵循构造规则,但是当我们删除"b"指针(基指针)时,我们发现只有基本析构函数被调用.但这不能发生.要做适当的事情,我们必须使基础析构函数成为虚拟的.现在让我们看看下面发生了什么:

#include 

using namespace std;

class Base
{ 
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    virtual ~Base(){
        cout << "Base Destructor called\n";
    }
};

class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};

int main()
{
    Base *b = new Derived1();
    delete b;
}

输出更改如下:

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

因此,基指针的破坏(在派生对象上进行分配!)遵循破坏规则,即首先导出然后是基数.另一方面,对于构造函数,没有像虚构造函数那样的东西.


@Murkantilism,"虚拟构造函数无法完成"确实如此.构造函数不能标记为虚拟.

3> Bill the Liz..:

在多态基类中声明析构函数是虚拟的.这是Scott Meyers的Effective C++中的第7项.迈尔斯继续总结,如果一个类有任何虚函数,它应该有一个虚析构函数,而不是类设计为基类或不是设计用于多态应声明虚析构函数.


+"如果一个类有任何虚函数,它应该有一个虚析构函数,那些不是基类或不设计为多态使用的类不应该声明虚析构函数.":是否存在有意义的情况打破这个规则?如果没有,让编译器检查这个条件是否有意义并发出错误是否不满意?
类可以设计为不通过某个类型的指针删除,但仍然具有虚函数 - 典型的例子是回调接口.一个人不会通过回调接口指针删除他的实现,因为它只用于订阅,但它确实有虚函数.
@dascandy没错 - 这或全部在这里我们使用多态的行为,但不通过指针执行存储管理的_many_其他情况下 - 例如保持自动或静态持续时间的对象,仅作为观察路线的指针.在任何此类情况下都不需要/目的来实现虚拟析构函数.由于我们只是引用了这里的人,我更喜欢上面的Sutter:"准则#4:基类析构函数应该是公共的和虚拟的,或者是受保护的和非虚拟的." 后者确保任何人意外地尝试通过基指针删除都会显示出他们的方式错误

4> BigSandwich..:

还要注意,在没有虚析构函数时删除基类指针将导致未定义的行为.我刚刚学到的东西:

如何覆盖C++中的删除行为?

我已经使用C++多年了,我仍然设法挂起自己.



5> yesraaj..:

当您的类具有多态性时,使析构函数成为虚拟的.



6> Abyx..:

通过指向基类的指针调用析构函数

struct Base {
  virtual void f() {}
  virtual ~Base() {}
};

struct Derived : Base {
  void f() override {}
  ~Derived() override {}
};

Base* base = new Derived;
base->f(); // calls Derived::f
base->~Base(); // calls Derived::~Derived

虚拟析构函数调用与任何其他虚函数调用没有什么不同.

因为base->f(),调用将被调度Derived::f(),并且它的base->~Base()覆盖函数是相同的- Derived::~Derived()将被调用.

在间接调用析构函数时也是如此,例如delete base;.该delete声明将调用base->~Base()将被发送到的声明Derived::~Derived().

具有非虚拟析构函数的抽象类

如果您不打算通过指向其基类的指针删除对象 - 那么就不需要有虚拟析构函数.只要protected这样就不会被意外调用:

// library.hpp

struct Base {
  virtual void f() = 0;

protected:
  ~Base() = default;
};

void CallsF(Base& base);
// CallsF is not going to own "base" (i.e. call "delete &base;").
// It will only call Base::f() so it doesn't need to access Base::~Base.

//-------------------
// application.cpp

struct Derived : Base {
  void f() override { ... }
};

int main() {
  Derived derived;
  CallsF(derived);
  // No need for virtual destructor here as well.
}



7> Dragan Ostoj..:

我喜欢考虑接口的接口和实现.在C++中,speak接口是纯虚拟类.析构函数是界面的一部分,有望实现.因此析构函数应该是纯虚拟的.构造函数怎么样?构造函数实际上不是接口的一部分,因为对象始终是显式实例化的.


这如何改善已经接受的答案?
这是对同一个问题的不同看法.如果我们考虑接口而不是基类和派生类,那么它就是自然的结论:如果它是接口的一部分而不是虚拟化.如果不是这样的话.
+1表示**接口**和C++**纯虚拟类**的OO概念的相似性.关于*析构函数应该被实现*:这通常是不必要的.除非类正在管理资源,例如原始动态分配的内存(例如,不通过智能指针),文件句柄或数据库句柄,否则使用编译器创建的默认析构函数在派生类中是正常的.请注意,如果析构函数(或任何函数)在基类中声明为"virtual",则它在派生类中自动为"virtual",即使未声明它也是如此.

8> 小智..:

简单来说,当您删除指向派生类对象的基类指针时,Virtual析构函数将以正确的顺序销毁资源.

 #include
 using namespace std;
 class B{
    public:
       B(){
          cout<<"B()\n";
       }
       virtual ~B(){ 
          cout<<"~B()\n";
       }
 };
 class D: public B{
    public:
       D(){
          cout<<"D()\n";
       }
       ~D(){
          cout<<"~D()\n";
       }
 };
 int main(){
    B *b = new D();
    delete b;
    return 0;
 }

OUTPUT:
B()
D()
~D()
~B()

==============
If you don't give ~B()  as virtual. then output would be 
B()
D()
~B()
where destruction of ~D() is not done which leads to leak



9> 小智..:

当您希望不同的析构函数在通过基类指针删除对象时遵循正确的顺序时,析构函数的虚拟关键字是必需的.例如:

Base *myObj = new Derived();
// Some code which is using myObj object
myObj->fun();
//Now delete the object
delete myObj ; 

如果派生类析构函数是虚拟的,那么对象将按顺序被驱逐(首先是派生对象然后是基础).如果派生类析构函数不是虚拟的,那么只会删除基类对象(因为指针是基类"Base*myObj").因此派生对象会有内存泄漏.

推荐阅读
爱唱歌的郭少文_
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有