第3章:资源管理
条款13:以对象管理资源
RAII(Resource Acquisition Is Initialization)使用
new获取资源后于同一条语句内以它初始化某个资源管理对象。利用当管理对象离开作用域被销毁时自动调用其析构函数的机制确保资源被释放。不直接用new和delete,多用智能指针。智能指针在其析构函数中做
delete动作而非delete[]动作因此,不要在动态分配来的数组身上使用智能指针(虽然它仍然会通过编译)。
条款14:在资源管理类中小心copying行为
- 当不想
RAII类被复制时可以继承Uncopyable类
将智能指针施于heap-based资源身上是个好主意,但并不是所有资源都是heap-based的,比如互斥器mutex,mutex就不可复制。
条款15:在资源管理类中提供对原始资源的访问
显示转换(安全)
例如,
shared_ptr类就提供可一个get成员函数来返回智能指针内部的原始指针。operator隐式转换(调用方便)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class A
{
...
// operator 可以实现隐式转换函数
operator B() const
{
return b;
}
...
private:
B b; // B 类型对象成员
};
// 接受 B 类型对象的一个函数
void func(B b);
A a;
func(a); // a 会被编译器隐式转换为 B 类型对象
条款16:使用new和delete时要采用相同的形式
编译器在给数组分配内存时,会包括数组大小的记录
这样的好处是,
delete[]时知道需要调用多少次析构函数。不要对数组形式进行
typedef动作
条款17:以独立语句将new来的对象置入智能指针
考虑这样两个函数:
1 | int priority(); // 调用这个函数不排除会发生异常 |
如果这样调用编译不通过,因为shared_ptr接收原始指针的构造函数是explicit的:
1 | processWidget(new Widget, priority()); |
如果这样调用可能会造成内存泄漏:
1 | processWidget(shared_ptr<Widget>(new Widget), priority()); |
因为编译器在编译这条语句时,实际上会创建三个动作:
- 调用
priority() - 执行
new Widget - 调用
shared_ptr的构造函数
而C++编译器安排这三个动作的次序是不确定的。
如果执行次序是这样:
- 执行
new Widget - 调用
priority() - 调用
shared_ptr的构造函数
一旦中途priority()导致异常,那么new返回的原始指针并未交给智能指针保管,将造成资源泄露。
正确的做法:
1 | shared_ptr<Widget> pw(new Widget); |
因为,编译器对“跨越语句的各项操作”没有重新排列执行次序的自由。