条款13: 以对象管理资源
动态分配的资源,很容易忘记delete,或者程序在某处return而没有执行delete。因此,用对象来管理资源(类对象,在区块结束后,自动调用析构函数释放所有资源) 。 //后面介绍的就是用 智能指针 这种对象,替代原始的指针管理资源(就是指向那个资源)
以对象管理资源的两个关键想法:
a. 获得资源后立即放进管理对象
b. 管理对象运用析构函数确保资源被释放
auto_ptr是个“类指针(pointer-like)对象”,就是所谓的“智能指针”。
为防止多个对象(即智能指针对象)指向同一个资源,导致这个资源被delete多次。auto_ptr有个不同寻常的性质: 通过拷贝构造或拷贝赋值复制它们,它们会变成null,而赋值所得指针将取代资源的唯一拥有权。
“引用计数型指针”(reference-counting smart pointer; RCSP)。 也是个智能指针。持续追踪共有多少对象指向某资源,并在无人指向它时自动删除资源。 但RCSP无法打破环状引用(互相指,好像都还在“被使用”)。TR1 的 tr1::shared_ptr就是个RCSP。
auto_ptr和tr1::shared_ptr两者都是在其析构函数内做 delete 而不是 delete[] 动作 (条款16描述)。
没有针对“C++动态分配数组”而设计的东西。因为vector和string几乎可以取代动态分配而得的数组。
Tips: 1. 为防止资源泄露,使用“RAII(资源取得之时便是初始化之时)对象”,他们在构造函数中获得资源并在析构函数中释放。//就是用智能指针 取代原始指针,指向资源对象,从而控制资源
2. 两个常用RAII classes 分别是 tr1::shared_ptr 和 auto_ptr 。前者copy行为比较直观。后者,复制动作会使它指向null。
条款14: 在资源管理类中 小心 coping 行为
//理解不深 以后再看
条款15: 在资源管理类中提供对原始资源的访问
// 有时API需要原始指针,比如大量 C API 。那么用智能指针管理对象时,就必须提供一个转换方法,返回资源对象的原始指针。
Tips: 1. API要求访问原始资源,所以每一个RAII class 应该提供一个“取得其所管理之资源”的办法
2. 对原始资源的访问,可能经由显示转换 或 隐式转换。 显式转换安全,隐式方便。
条款16: 成对使用 new 和 delete 时要采用相同形式
当你使用 new , 有两件事发生:
- 内存被分配出来(通过名为operator new的函数,条款59和51)
- 针对此内存,会有一个(或更多)构造函数被调用。
当你使用 delete ,也有两件事:
- 针对此内存,会有一个(或更多)析构函数被调用。
- 然后,内存才被释放(通过名为 operator delete的函数)
单一对象的内存布局,一般而言不同于数组的内存布局。
数组所用内存通常包括“数组大小”的记录,以便delete 知道需要调用多少次析构函数。
比如,可以想象内存布局如下,n是数组大小: (编译器不一定非这么实现,但很多的确是这样做的)
------------
| Object |
------------
-----------------------------------------------------
| n || Object || Object || Object || Object |
-----------------------------------------------------
例如: std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
……
delete stringPtr1; //删除一个对象
delete [] stringPtr2; // 删除一个由对象组成的数组
如果对stringPtr1 使用 "delete []" 形式,结果未有定义。因为,delete会读取若干内存并将它解释为“数组大小”,然后再调用析构函数。
【规则很简单】: new时使用 [ ] , delete时也使用 [ ] ; new时没有使用 [ ] , delete时也不用 [ ]
尽量不对数组形式做 typedef 动作,因为容易让人分不清究竟是用何种delete。这容易做到,因为C++标准库含有 string,vector等templates,可将数组要求降至几乎为零。 // 理解不深,有空温习一下
条款17:以独立语句将 newed 对象置入只能指针
例:
int priority()
void processWidget(std::tr1::shared_ptr<Widget> pw , int priority)
现在调用
processWidget(std::tr1::shared_ptr<Widget>(new Widget) , priority() ) // 可能泄露资源!!
原因: 编译器产出一个 processWidget 调用代码前,必须先核算即将传递的各个实参。
上述第二个实参,是单纯 priority 函数的调用。
但,第一个实参 std::tr1::shared_ptr<Widget>(new Widget) 分成两部分:
-
- 执行 new Widget
- 调用 tr1::shared_ptr 构造函数
于是,在调用 processWidget 之前,编译器必须创建代码,做三件事:
- 调用 priority
- 执行 new Widget
- 调用 tr1::shared_ptr 构造函数
但C++ 编译器执行这三件事的顺序弹性很大(其他语言不一定,如java总以特定顺序完成参数核算)。
可以确定, new Widget 一定执行于 tr1::shared_ptr 构造函数前, 因为要被用到。
但是,调用 priority 可以放在任何位置执行。
如果放在中间,比如
- 执行 new Widget
- 调用 priority
- 调用 tr1::shared_ptr 构造函数
那么,当 2 中出现异常。 new Widget 返回的指针就会遗失,造成资源泄漏。
避免此类问题方法: 分离语句。
- 创建 Widget
- 将它置入一个只能指针
- 把智能指针传给 processWidget
std::tr1::shared_ptr<Widget> pw ( new Widget ); // 单独语句内只能指针存储 new 所得对象
processWidget(pw , priority() ); // 这个动作绝不会造成泄漏
编译器对于”跨越语句的各项操作“没有重新排列的自由(只有语句内,才有那个自由度) // 拆开的语句,对执行顺序限定的好!