×

析构函数的特征 函数 析构

析构函数的特征(C++ 究竟什么时候该用虚析构函数)

admin admin 发表于2023-02-03 06:38:48 浏览38 评论0

抢沙发发表评论

本文目录

C++ 究竟什么时候该用虚析构函数

C++中明确指出,当派生类对象经由一个基类指针被删除,而此基类仅提供了non-virtual析构函数,其结果是未定义的。如果删除基类指针,则需要运行基类构造函数并清除基类的成员,如果对象实际是指向派生类类型的对象,则没有定义这种行为。实际执行是通常发生的是对象的derived成分没有被销毁,而派生类中的base class成分通常会被销毁,这就造成了一个“局部销毁”的对象,从而造成资源泄露。消除这个问题很简答:给base class一个virtual析构函数,这会保证运行适当的析构函数。

所以,如果类要作为父类的话,要定义虚析构函数。

即使class完全不带virtual函数,就要注意了,不要试图去继承它。如标准的string和标准库中的容器vector,list,set等都不带有任何的virtual函数,如果你试图写这样的代码:

class SpecialString:pulic std::string{...};

文章刚开始提到过的问题,你是不是能够避免呢?有时候你会不自觉的义正言辞的去做的。拒绝诱惑吧,毕竟C++没有提供类似于Java的final class禁止派生机制。

有时候令class带有一个pure virtual析构函数,可能会带来便利,如Meyers在《More Effective C++》中“条款33:将非尾端类设计为抽象类”曾经提到过的那样。

这样Base成为一个abstract class,当有时候你希望拥有abstract class,但是没有任何pure virtual函数怎么办?很简答:为你希望成为abstract class的那个class声明一个pure virtual析构函数吧。虽然abstract class不能被实例化,但是别忘了需要为pure virtual析构函数提供一个定义,这也只能在类外进行了:

Base::~Base(){} //pure virtual析构函数的定义

毕竟,派生类析构函数还是回调用此函数来析构其相应部分的。

最后请记住:

1) 带多态性质的基类应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。

2) 类的设计目的如果不是作为基类使用,或者不是为了具备多态性,就不应该声明virtual析构函数。

C++中有些类的析构函数也被定义为虚函数,这样做有什么用

在阅读C++项目(caffe)源码时,发现不少基类不仅把常规的成员函数定义成虚函数(virtual),也会把析构函数定义为虚函数,稍稍思考下,这样做的确是有原因的,本文将结合C++代码实例尝试探讨下。

常规

随便写一段C++代码作为实例,在这个例子中,我们先不把析构函数定义为虚函数:

这段代码的逻辑很简单,无非就是定义了两个类:类 Base 的成员函数 foo() 为虚函数,构造函数和析构函数都是常规函数,此外它还有个 public 的成员变量 buf。类 Child 则公开继承了 Base,因此它可以直接使用 Base::buf——在构造函数中 new了一段内存,并且在析构函数 delete 掉它。

Child c;

c.foo();

我们直接使用 Child 实例化一个对象 c,调用 c.foo(),此时得到如下输出:

一切尽在预料中。

不安全的问题

虽说对象 c 调用 foo() 的输出完全符合预计,但像上面那样定义类仍然是非常危险的做法。在这一节我们曾讨论过,父类指针可以调用派生类的重写函数,因此下面这两行C++代码也是合法的,请看:

编译这段C++代码完全没有问题,运行也不会报错,输出如下:

Base construct

Child construct

Child::foo

Base deconstruct

可是,从输出信息能够看出,派生类 Child 的析构函数没有被调用,对于本例而言,new 出来的 buf 没有对应的 delete,势必会造成内存泄漏。

解决问题

要解决所谓的“不安全问题”,其实很简单,按照题目说的做——将基类的析构函数也定义为虚函数就可以了,请看修改后的C++代码:

也即尽在基类 Base 的析构函数前加上 virtual 关键字,其他的所有代码都无需改动。现在再执行下面的这几行C++代码:

输出如下:

显然,此时派生类 Child 的析构函数也会被调用了,内存泄漏的问题倍解决了。

小结

C++ 中的 virtual 关键字是非常好用,也是C++程序员必须掌握的关键字,其实,“不安全问题”出现的原因也是简单的:我们在静态类型与动态绑定一节中提到过,基本上只有涉及到 virtual 函数时,才会发生动态绑定,此时通过对象指针(pb)调用的函数由它指向的类(Child)决定,所以此时派生类 Child 的析构函数会被调用。如果基类 Base 的析构函数不是虚函数,那么对象指针(pb)调用的函数由其静态类型(Base)决定,也即调用的其实只是基类 Base 的析构函数而已。