资源管理
条款 13 以对象管理资源
“以对象管理资源“”也被称为“资源取得时机便是初始化时机(RAII)”。
- 获得资源后立即放进管理对象内,即在构造函数中获取资源。
- 管理对象(managing object)运用析构函数确保资源被释放。
在C++11中,应该使用 shared_ptr
和 unique_ptr
来管理指针,因为C++的RAII机制会在两者的析构函数中自动 delete
掉指针。
两者区别 :
shared_ptr
内部管理一个“被引用计数器”,用来指示有多少个shared_ptr
管理同一指针,当“被引用计数器“的值变为 0 时,该指针被释放。unique_ptr
只能管理一个指针,多个unique_ptr
不能同时管理一个指针,因为其内部是禁止复制的。
注意 :
- 智能指针析构函数执行
delete
而不是delete[]
动作,因此不要把动态分配数组交给智能指针管理,虽然能通过编译。 - C++中没有“动态分配数组”的设计,C++17后支持,也可以使用boost的
boost::scoped_array
和boost::shared_array
。
*请记住 : *
1. 为防止资源泄漏,请使用RAII对象,它们在构造函数中获取资源并在析构函数中释放资源。
2. 两个被使用的 RAII classes
分别是 tr1::shared_ptr
和 tr1::unique_ptr
,前者是较佳选择,因为其 copy 行为比较直观。后置被禁止赋值。
条款 14 在资源管理类中小心 copying 行为
当你自定义 RAII 对象时,如果只是进行简单的赋值,会出现多个RAII对象管理同一资源,当这些对象调用析构函数时,这个资源将会被多次释放,产生错误。
因此"当一个 RAII 对象被复制"应当 :
- 禁止复制,例如
unique_ptr
。 - 对底层资源祭出“引用计数法”,当对象出现复制行为时,“被引用次数”递增,例如
shared_ptr
。
通常我们会在 RAII classes
内含一个 shared_ptr
成员变量,把原来的指针交给智能指针管理即可,如果我们有时候在资源释放时不仅仅是 delete
掉它,还要进行其他操作,比方说互斥器的解锁,那么可以使用指针的“删除器“(函数指针或函数对象)。
例
class Lock{
public:
explicit Lock(Mutex *pm) : mutexPtr(pm, unlock)//unlock为函数指针或函数对象
{
lock(mutexPtr.get());
}
private:
std::shared_ptr<Mutex> mutexPtr;
};
- 复制底部资源,也就是所谓的“深度拷贝”,即复制 RAII 对象时,并不是复制其管理的指针,而是复制该指针指向的“底部资源”,这样就可以防止同一资源被释放多次了。注意默认的复制函数是不会进行“深度拷贝”的。
- 转移底部资源的拥有权,也就是
auto_ptr
,但是在 C++11中已经被弃用,可以使用unique_ptr
代替。
//使用右值引用转移unique_ptr的所有权
std::unique_ptr<Test> t_ptr1(new Test);
std::unique_ptr<Test> t_ptr1 = std::move(t_ptr1);
*请记住 : *
1. 复制 RAII 对象必须复制它所管理的资源,所以资源的 copying 行为决定 RAII 对象的 copying 行为。
2. 普遍常见的 RAII class copying 行为是 : 抑制 copying 、施行引用技术法。不过其他行为也可能被实现,例如深度拷贝和所有权转移。
条款 15 在资源管理类中提供对原始资源的访问
当你需要使用 C APIs 的时候,RAII 对象往往需要向用户提供原始资源(即所管理的指针)。
有两种方式 :
-
显式转换
-
隐式转换
class Font{
public:
//显式转换
FontPtr get()
{
return f;
}
//隐式转换
operator FontPtr() const
{
return f;
}
private:
FontPtr f;
}
void changeFontSize(FontPtr f, int newSize);
//.c
Font f;
changeFontSize(f,10);//隐式转换,调用f.operator FontPtr()
changeFontSize(f.get(),20);//显式转换
虽然这样做容易带来资源泄漏的问题,但当你确实需要的时候也是可以这样做的。
*请记住 : *
1. APIs 往往要求访问原始资源,所以每一个 RAII class 应该提供一个“取得其所管理的资源”的方法。
2. 对原始资源的访问可能经由显式转换或隐式转换,一般而言显式转换比较安全,但隐式转换对客户比较方便。
条款 16 成对使用 new 和 delete 时要采取相同形式
规则 :
- 调用 new 时使用 [],对应的调用 delete 时也要使用 []。
- 最好不要对数组形式做
typedef
、using
之类的动作。
*请记住 : *
1. 如果你在 new 表达式中使用 [] ,必须在相应的 delete 表达式中也是用 []。如果你在 new 表达式中不使用 [],一定不要在 delete 中使用 []。
条款 17 以独立语句将 newd 对象置入智能指针
举个例子 :
int priority();
void processWidget(std::shared_ptr<Widget> pw, int priority);
//调用
processWidget(std::shared_ptr<Widget>(new Widget),priority());
调用 processWidge()
时其参数内的语句执行顺序弹性很大,没有确定的执行顺序。
假设 :
- 执行
new Widget
- 调用
priority()
- 调用
shared_ptr
构造函数
若第二步抛出异常,指针将会遗失,导致资源泄漏。
**解决方法 : **使用分离语句。
std::shared_ptr<Widget> pw(new Widget);
processWidget(pw,priority());
*请记住 : *
1. 以独立语句将 newd 对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。