Effective C++ Notes
Item 07 : 为多态基类声明virtual析构函数
1 #include <iostream> 2 using namespace std; 3 4 class Base { 5 public: 6 Base() : bValue(1) {} 7 virtual ~Base() { cout << "Base destructor" << endl; } 8 // ~Base() { cout << "Base destructor" << endl; } 9 virtual void print() { 10 cout << "print : base" << endl; 11 } 12 private: 13 int bValue; 14 }; 15 16 class Derived : public Base { 17 public: 18 Derived() : dValue(100) {} 19 ~Derived() { 20 cout << "Derived destructor" << endl; 21 } 22 void print() { 23 cout << "print : derived" << endl; 24 } 25 private: 26 int dValue; 27 }; 28 29 int main() { 30 Base* p = new Derived(); 31 p->print(); 32 delete p; 33 }
注意Line 7和8,当基类的析构函数用的是Line7, 即基类析构函数是virtual时,Line32处执行delete p时,相当于调用p所指的析构函数,由于p是一个指向派生类对象的基类指针,且基类中析构函数是virtual的,所以满足了多态的发生条件,即此处调用的是派生类的析构函数Line19。派生类在析构的时候会自动调用其基类的析构函数,所以就有了下图的结果。
假如基类的析构函数不再是virtual的 (即将Line7注释掉,换成Line8),这样在Line32处调用delete p时, p本身是个基类指针,但是所指向的又是一个派生类对象。此时如果p所调用的函数是virtual的,那么p就直接调用派生类中的同名函数,即上面所描述的那样;若p所调用的函数不是virtual的,那它就老老实实的调用基类的函数。
所以将基类的析构函数换成non-virtual之后(即注释掉Line7换成Line8之后),结果如下:
这样导致的结果就是p所指的派生类对象只有基类部分得到释放,派生类对象中独属于派生类的部分仍留在堆上,导致内存泄漏。
==========================================================================================================
1 #include <iostream> 2 using namespace std; 3 4 class Point { 5 public: 6 Point(int xCoord, int yCoord); 7 ~Point(); 8 private: 9 int x, y; 10 }; 11 12 class Point2 { 13 public: 14 Point2(int xCoord, int yCoord); 15 virtual ~Point2(); 16 private: 17 int x, y; 18 }; 19 20 class Point3 { 21 public: 22 Point3(int xCoord, int yCoord) : x(xCoord), y(yCoord) {} 23 ~Point3() {} 24 private: 25 int x, y; 26 }; 27 28 int main() { 29 cout << sizeof(Point) << endl; 30 cout << sizeof(Point2) << endl; 31 Point* p; 32 Point3 obj(1,2); 33 return 0; 34 }
这段代码想要表达两个问题:
1. 虚函数由于vptr,vtbl,会占用一些额外空间。Point和Point2的差别就在于析构函数是否是virtual,输出结果为8,12.
2. 我把Point3这个class放在这的目的是想说明,对于Point和Point2这样的,类内部的构造函数、析构函数只有声明没有定义(都直接以;结尾的),在其成员函数-没有定义的情况下不妨碍对它们求sizeof和定义指针(L31),但是它们不能定义对象(像L32那样)。
============================================================================================================
Item 29 : 为“异常安全” 而努力是值得的
经验:异常安全函数即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种 可能的保证: 基本型-->发生异常,程序处于某个合法状态 强烈型-->发生异常,程序处于原先状态 不抛异常型-->承诺绝不抛出异常 class PrettyMenu { public: void changeBackground(istream& imgSrc); private: Mutex mutex; //由于这个class希望用于多线程环境,所以它有这个互斥器作为并发控制之用 Image* bgImage; //目前的背景图像 int imageChanges; //背景图像被改变的次数 }; void PrettyMenu::changeBackground(std::istream &imgSrc) { lock(&mutex); //取得互斥器 delete bgImage; //摆脱旧的背景图像 ++imageChanges; //修改图像变更次数 bgImage = new Image(imgSrc); //安装新的背景图像 unlock(&mutex); //释放互斥器 } 解析: 泄漏资源:new Image(imgSrc) 导致异常的话,互斥器就不会 unlock 数据败坏:new Image(imgSrc) 导致异常的话,bgImage指向一个已删除的对象,imageChanges 也已被累加