博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
读书笔记_Effective_C++_条款七:为多态基类声明virtual析构函数
阅读量:5136 次
发布时间:2019-06-13

本文共 2473 字,大约阅读时间需要 8 分钟。

 

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

1 class Shape 2 { 3 private: 4          int color; 5 public: 6          virtual void print() = 0; 7 }; 8  9  10 class Circle: public Shape11 {12 private:13          double radius;14 public:15          void print(){cout << "This is a Circle" << endl;}16 };17 18 class Square: public Shape19 {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 Shape2 {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 Shape2 {3 private:4          int color;5 public:6          virtual void print() = 0;7          virtual ~Shape(){}8 };

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

Shape obj;

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

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

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

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

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

 

总结一下:

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

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

转载于:https://www.cnblogs.com/jerry19880126/archive/2013/03/18/2966298.html

你可能感兴趣的文章
SQL语法(3)
查看>>
在js在添版本号
查看>>
Maximum Gap
查看>>
sublime3
查看>>
[转]快速矩阵快速幂
查看>>
CMap的使用(转)
查看>>
Exception Type: IntegrityError 数据完整性错误
查看>>
《浪潮之巅》十八十九章笔记
查看>>
Power Strings
查看>>
[转载]Hash
查看>>
Nuget:Newtonsoft.Json
查看>>
你是这样理解shell编程的嘛?
查看>>
前端性能优化之重排和重绘
查看>>
Assets和Raw区别
查看>>
【luogu4185】 [USACO18JAN]MooTube [并查集]
查看>>
手机号脱敏处理
查看>>
CI控制器调用内部方法并载入相应模板的做法
查看>>
Hdu - 1002 - A + B Problem II
查看>>
HDU - 2609 - How many
查看>>
每天CookBook之Python-003
查看>>