最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
1 多态的基类需要虚构函数
class TimeKeeper{ //计时器类,用来当做基类 public: TimeKeeper(); //这是构造函数 ~TimeKeeper(); //这是析构函数 ...... }; class AtomicClock : public TimeKeeper{...}; //原子钟是一种计时器 class WaterClock : public TimeKeeper{...}; //水钟也是一种计时器 TimeKeeper* getTimeKeeper(){...} //用来返回一个动态分配的基类对象,工厂类 TimeKeeper* ptk = getTimeKeeper(); //使用这个基类指针 *** 作它的子类,假设这个子类是WaterClock ..... delete ptk; //使用完毕,释放资源
上述代码的基类没有虚析构函数,直接 delete 基类指针指向的派生类(delete ptk;),会导致派生类 WaterClock 中的基类成分( 即 TimeKeeper 的成员变量)被释放,而专属于派生类 WaterClock 内的成员变量没被释放,且它的析构函数也未能执行,于是造成了一个诡异的局部销毁对象,这也是造成内存泄漏、败坏数据结构、在调试器上浪费时间的原因之一。
解决方法:给多态的基类的析构函数添加 virtual 关键字
class TimeKeeper{ //计时器类,用来当做基类 public: TimeKeeper(); //这是构造函数 virtual ~TimeKeeper(); //这是虚析构函数 ...... };
此后 delete 基类指针指向的派生类,派生类中的所有成员变量都会被释放。
2 虚函数的工作原理
虚函数是用来在运行时(runtime),自动把编译时未知的对象,比如用户输入的对象,和它所对应的函数绑定起来并调用。当一个类中有虚函数时,编译器会给这个类添加一个隐藏变量 vptr,即虚函数表指针(virtual table pointer),vptr 指向一个由函数指针构成的数组 vtbl,即虚函数表(virtual table)。当对象调用某一虚函数时,具体调用哪个函数取决于该对象的 vptr 所指的那个 vtbl——编译器在其中寻找适当的函数指针。
如果类内有虚函数,其对象的体积会增加,因为 vptr 、vtbl都需要占用空间的,所有不要无谓声明虚析构函数。
3 不要继承标准库中的类
class SpecialString : public std::string{...}; //某个继承自标准字符串的类 SpecialString* pss = new SpecialString("Hi"); std::string* ps; ... ps = pss; delete ps; //使用完后从基类删除内存
这样的写法会导致内存泄漏,因为标准库的字符串并没有把析构函数定义为虚函数,它们并不是用来拿去继承的,所以不能随便继承,包括 STL。虽然C++不像Java有final和C#有 sealed 来阻止某些类被继承的机制,我们也要拒绝这种写法。
4 抽象类
抽象类是包含至少一个纯虚函数的类,而且它们不能被实例化,只能通过指针来 *** 作,是纯粹被用来当做多态的基类的。
因为多态的基类需要有虚析构函数,抽象类又需要有纯虚函数,那么在抽象类中就要把析构函数声明为纯虚函数,如下述代码
class AWOV{ public: virtual ~AWOV() =0; //"=0"只是一个关键字,用来声明纯虚函数,并不把任何东西设为0 }; AWOV::~AWOV(){ //纯虚析构函数的定义 }
Note
- 带多态性质的基类应该声明一个虚析构函数;如果 class 内至少有一个虚函数,应该为它声明虚析构函数
- 不是所有基类都需要声明虚析构函数的,如条款06中的 Uncopyable 类,只是为了实现一个功能,而不具有多态性质。
条款08:别让异常逃离析构函数
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)