Effective C++——》条款7:为多态基类声明virtual析构函数 .

严格来说,多态分为编译时多态运行时多态编译时多态是函数的重载,而运行时多态则是迟绑定技术,即根据基类指针或引用的实际指向来决定采取何种行动一般来说,多态特指运行时多态。下面我们来举有关C++多态的一个简单例子:

 1 class Shape
 2 {
 3 private:
 4          int color;
 5 public:
 6          virtual void print() = 0;
 7 };
 8 
 9  
10 class Circle: public Shape
11 {
12 private:
13          double radius;
14 public:
15          void print(){cout << "This is a Circle" << endl;}
16 };
17 
18 class Square: public Shape
19 {
20 private:
21 
22          double length;
23 public:
24          void print(){cout << "This is a Square" << endl;}
25 };

多态前提是继承,如上面这个例子,Shape是基类,它有一个所有形状都具有的属性:颜色。Circle和Square派生自Shape,它们都有颜色的属性,但同时Circle还有半径的属性,Square有边长的属性。这里print()函数是一个有多态行为的函数,比如:

1 Shape *p1 = new Square;
2 p1->print(); // 这时的运行结果就是“This is a Square”
3 
4 Shape *p2 = new Circle;
5 p2->print(); // 这时的运行结果就是“This is a Circle”

但这里会有一个问题,如果接下来操作:

delete p1;
delete p2;

就会出现内存的泄露,为什么会这样?因为Shape没有显式定义析构函数编译器为我们生成的析构函数不是虚的,这时候delete p1,编译器就简单地析构了基类的部分,派生类的部分就被架空了,造成了内存的泄露。解决方法是这样:

1 class Shape
2 {
3 private:
4          int color;
5 public:
6          virtual void print() = 0;
7          virtual ~Shape(){}
8 };

有了virtual关键字,析构时就会检查指针实际指向的对象,也就会先定位到相应的派生类,完成派生类的析构之后,再析构基类。那么是怎么“检查”指针实际指向的对象呢?C++为每个含有virtual关键字类中提供了一个虚表指针vptr,它指向一个由函数指针构成的数组,称为虚表。程序在运行时会维护类对象中的vptr,比如:

Shape *p1 = new Square;

vptr就会指向Square的print()函数。

那是不是为了安全起见,将所有的析构函数都加上virtual呢?这样的确可以防止多态过程中析构造成的内存泄露,

 

但同时也有问题,即一旦出现了virtual,编译器就会生成vptr,这个vptr本身是要占空间的,对于一个并不大的类而言,vptr所占空间(32位机上是4字节,64位机上是8字节)的百分比还是很可观的。所以virtual要相时而加,出现多态继承关系时一定要加上,而非基类或者不用于多态的基类(比如条款6说的Uncopyable)就不要加

 

另外,所有的STL,包括string,vector等等,在设计时就没把它们当作基类,所以它们的析构函数都不是虚函数,因此试图定义一个类去继承STL是非常危险的!

 

再回过头来看之前的例子:

1 class Shape
2 {
3 private:
4          int color;
5 public:
6          virtual void print() = 0;
7          virtual ~Shape(){}
8 };

注意到基类的print()函数不仅仅是虚的(含有virtual关键字),而且这个函数还是纯虚函数(有“=0”),纯虚函数所在的类是抽象类,是不能定义对象的,比如:

Shape obj;

就不能通过编译。使不想生成对象的类成为抽象类,这是一个很好的技术实现方案,但万一有时候难以找到一个纯虚函数,怎么办呢?自己强行加一个像print()一样的函数固然是可以的,但不自然,一个好的方法是把它的析构函数变成纯虚函数,像这样:

1 class Shape
2 {
3 private:
4          int color;
5 public:
6          virtual ~Shape() = 0;
7 };

问题算是解决一半了,还有一个问题,编译器会在Shape派生类的析构函数中创建一个对~Shape()的调用动作,如果不实现这个~Shape(),链接器会报错,一个解决方案就是简单地加上括号,像这样:

1 class Shape
2 {
3 private:
4          int color;
5 public:
6          virtual ~Shape() = 0{};
7 };

好了,这下Shape既成了抽象类(不能拥有对象),也能在继承链中妥善地处理析构的内存问题。

 

总结一下:

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

 

(2)     若一个类不作为基类,或者不具备多态性(比如Uncopyable),就不该声明virtual析构函数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明
### 回答1: 如果基类析构函数不是虚函数,它不会被其导出类的析构函数覆盖,导致在析构多态对象时不会调用导出类的析构函数。这可能会导致资源泄漏或其他错误。 举个例子,假设我们有一个基类 Shape 和它的导出类 Circle。如果 Shape 的析构函数不是虚函数,那么当我们删除一个 Circle 类型的对象时,它只会调用 Shape 的析构函数,而不是 Circle 的析构函数,因此 Circle 可能分配的任何资源都不会被正确释放。 因此,如果您有一个多态继承结构,请确保基类析构函数是虚函数。 ### 回答2: 基类析构函数不是虚函数,会带来以下几个问题: 1. 对象的内存泄露:当基类指针指向派生类对象时,如果基类析构函数不是虚函数,那么当使用delete操作符删除这个基类指针时,只会调用基类析构函数,而不会调用派生类的析构函数。这样会导致派生类对象中的资源没有被正确释放,造成内存泄漏。 2. 对象切割:在基类指针指向派生类对象的情况下,如果基类析构函数不是虚函数,那么当使用基类指针删除对象时,只会调用基类析构函数,而不会调用派生类的析构函数。这样会导致对象的派生类部分没有得到正确的析构,可能导致资源泄露或行为异常。 3. 引发未定义行为:如果基类析构函数不是虚函数,且派生类有带有资源占用的成员变量时,当使用基类指针删除派生类对象时,派生类的析构函数将不会被调用。这样会导致派生类对象中的资源没有得到正确释放,可能造成程序行为的未定义。 综上所述,基类析构函数不是虚函数会导致对象的内存泄露、对象切割、以及引发未定义行为。为了避免这些问题,应该将基类析构函数声明为虚函数,以确保在删除对象时能够正确调用派生类的析构函数,从而正确释放资源。 ### 回答3: 基类析构函数不是虚函数会导致多态中的对象销毁时不会调用派生类的析构函数,而只会调用基类析构函数。这可能会引发以下问题: 1. 对象不完全销毁:如果基类析构函数不是虚函数,则当我们使用基类指针指向派生类对象时,通过基类指针删除该对象时,只会调用基类析构函数,而不会调用派生类的析构函数。这将导致派生类对象的资源无法正确释放,可能会导致内存泄漏或资源泄漏。 2. 数据丢失:如果基类指针指向派生类对象,并且我们将该指针赋值给派生类对象的另一个派生类指针,然后使用该派生类指针删除对象,由于基类析构函数非虚,只会调用基类析构函数,不会调用派生类的析构函数。这将导致派生类对象的成员数据无法正确释放,可能会导致数据丢失或程序行为异常。 3. 无法正确释放资源:在多态中,我们通常通过基类指针来管理派生类对象,当需要释放基类指针所指向的对象时,如果基类析构函数不是虚函数,只会调用基类析构函数,而不会调用派生类的析构函数。这将导致派生类对象中的资源无法正确释放,可能会导致资源泄漏或导致程序异常。 因此,为了避免上述问题,通常在基类中的析构函数应该声明为虚函数,以确保派生类对象在销毁时能够正确调用派生类的析构函数,从而保证资源的正确释放和程序的正常运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值