C++我们必须要熟悉的事之具体做法(2)——资源管理
1. 以对象管理资源
背后的思想:构造函数获取资源,在析构函数中释放资源。
获得资源后立刻放进管理对象:使用RAII(Resource Acquisition Is Initialization):资源获取时机便是初始化时机。
管理对象运用析构函数确保资源被释放:一旦对象被销毁,其析构函数会自动被调用,资源被释放。
,
常见的RAII对象auto_ptr(这个会造成资源的所有权转移,C++11已经废弃),C++11中的unique_ptr, shared_ptr, weak_ptr。
后面三个是最主要的使用手段。
实例:
testclass *create(); //返回对上创建的对象,需要delete
void func()
{
testclass *p = create();
可能由于过早return,抛出异常等等,没有执行下面的delete,造成资源泄露。
delete p;
}
好的做法:
#include <memory>
using std::unique_ptr;
void func()
{
unique_ptr<testclass> p(create()); //RAII手法。
}
由于unique_ptr禁止复制和赋值,所以当我们想要这些操作时我们需要考虑使用有引用计数的智能指针shared_ptr,
但是它可能会造成环状引用,所以此时我们还需要使用weak_ptr打破环状引用。
对于智能指针的具体使用参见另外一篇博客:
我们最后所要说的问题是:
在堆上分配一个变量,使用unique_ptr或shared_ptr:
unique_ptr<int> ip(new int(100)); //类似情况
在堆上分配一个数组使用:
vector<int> ivec(10); //我比较倾向这个和unique_ptr
unique_ptr<int[]> arr(new int[10]); //shared_ptr不可以这么用,必须像下面这样用
shared_ptr<int> arr(new int[10], std::default_delete<int[]>()); 或者 shared_ptr<int> arr(new int[10], [](int *p) {
delete[] p;
});
2. RAII需要考虑的问题——怎样复制资源管理类
有些资源不是在堆上的,我们要怎么做,例如:
void lock(mutex *pm); //锁住pm所指的互斥量
void unlock(mutex *pm); //解锁
现在创建一个使用RAII的class mylock:
class mylock()
{
public:
explicit mylock(mutex *pm) : pmutex(pm)
{
lock(pmutex);
}
~mylock()
{
unlock(pmutex);
}
private:
mutex *pmutex;
}
在我们mylock l1 = l2;复制mylock对象的时候我们要怎么做呢?
常见做法:前两个最常见
禁止复制:类似于unique_ptr的做法,具体做法参见前一篇博客。
对底层使用引用计数:类似与shared_ptr在引用为零时才删除具体对象。
具体做法:
class mylock()
{
public:
explicit mylock(mutex *pm) : pmutex(pm, unlock) //当引用计数为0时,使用unlock作为删除器
{
lock(pmutex.get());
}
//不再自己申明析构函数,依赖编译器的默认行为,因为pmutex是一个普通对象,可以使用自己的析构函数。
private:
std::shared_ptr<mutex> pmutex;
}
复制底部对象: 这个是”值语义”,shared_ptr也可以使用值语义实现。
一份资源可以拥有多份副本,在我们不需要的时候就可以释放它。此时复制对象需要“深度复制”。
转移底部资源所有权:类似于auto_ptr的实现。此时所有权会转移。
3. RAII类提供对原始资源的访问
unique_ptr、shared_ptr都提供了对原始资源访问的get()。
这个使我们的设计参考,在访问原始资源时主要的方法就是:
显示访问:get()方式,好处是不容易出错,坏处是对客户不方便。
隐式访问:testclass operator handle() const;好处是使用对客户比较方便,坏处是我们不需要的地方发生隐式转换。
我自己倾向于get():以不易出错为标准
4. 智能指针new的方式:以独立语句将new的对象放入智能指针,否则可能会造成资源泄露。
原因是在同一个语句中表达式的执行顺序不固定。
fun(shared_ptr<testclass>(new testclass), other());
new之后可能会执行other(),而other可能会抛出异常,造成资源泄露。最后才是执行智能指针初始化。