资源需要在使用完之后就归还给系统,如果不这么做,糟糕的是就会发生。C++程序中常用的资源就是动态内存分配(使用完不归还会导致内存泄露),但是内存知识需要被管理的众多资源之一。其他常见的资源还包括文件描述器、互斥锁、图形界面中的字型和笔刷、数据库连接以及网络sockets。
通常动态分配内存后,需要归还时都知道只需要delete就行了,但是这么做的问题在于必须确保程序确实有执行到这一步,可能程序在执行delete操作之前就执行了return语句或者抛出了异常,则可能不会对内存进行释放。因此单纯的依赖函数会执行delete语句是行不通的。
为确保资源总能被释放,可以将资源放入到对象内,当控制流离开当前函数,该对象的析构函数将自动释放那些资源。许多资源被动态分配与heap内后被用于单一区块或函数内。他们应该在控制流离开那个区块或是函数时被释放,标准库中提供了智能指针正是针对这种情况设计的产品,关于计数型智能指针的相关知识之前也在别处看过了。
并非所有的资源都是heap-based(在堆中分配的对象),最那种资源而言,智能指针往往不适合作为资源管理者,偶尔需要自己建立资源管理类。
假设使用C API函数处理类型为Mutex的互斥器对象,共有lock和unlock函数使用,并建立一个资源管理类:
void lock(Mutex* pm); //锁定pm指向的互斥器
void unlock(Mutex* pm); //将互斥器解除锁定
class Lock{
public:
explicit Lock(Mutex* pm):mutexptr(pm){ //构造函数初始指针
lock(mutexptr); //并锁定所指向的互斥器
}
~Lock(){
unlock(mutexptr); //析构函数中释放资源
}
private:
Mutex* mutexptr;
};
在正常调用的情况下没有什么问题,对象在区块或函数中被创建并管理某一个互斥器对象,在区块或函数结束时自动调用析构函数接触锁定。但是问题在于如果该Lock对象被复制,会发生什么事?
这是创建资源管理类的时候一定要想到的问题,通常会选择一下几种可能:
1.禁止复制。许多时候,资源管理类对象被复制是不合理的,这种时候应该禁止复制的发生:如何禁止。
2.对底层资源引用计数。有时会希望保有资源,直到它的最后一个使用者被销毁,这种情况下应该将该资源的“被引用数”递增。tr1::shared_ptr便是如此。所以通常只要在资源管理类中含有一个tr1::shared_ptr成员变量,就可以实现引用计数复制的行为。例如以上代码中将指针类型有Mutex改为tr1::shared_ptr
class Lock{
public:
explicit Lock(Mutex* pm):mutexptr(pm,unlock){ //构造函数初始指针,指定删除器
lock(mutexptr.get()); //并锁定所指向的互斥器
}
//不需要再声明析构函数,class的析构函数会自动调用非静态成员的析构函数,
//而mutexptr的析构函数会在引用计数为0时自动调用删除器
private:
std::tr1::shared_ptr* mutexptr;
};
.
3.复制底部资源。就是深拷贝,即复制对象的时候复制其所包裹的所有资源。
4.转移底部资源的拥有权。有时可能会需要确保永远只能有一个资源管理类对象指向一个未加工资源,即使在被复制的时候,也应该保证资源的拥有权从被复制物转移到目标物。
. 资源管理类可以帮助处理和资源之间的互动,避免资源泄露带来的问题。但是直接处理资源的情况也是时常发生的,需要绕过资源管理器对象直接访问原始资源。
比如使用智能指针指向某个对象,外部函数需要操作这个资源时需要的参数是该资源本身的类型参数,而不是这个指针对象,这时就需要一个函数可以将资源管理对象进行类型转换(转换为内含的原始资源)了。
几乎所有的智能指针都提供一个get()成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件)。
几乎所有的智能指针也重载了取值操作符(->和*),他们允许隐式转换至底部原始指针。
对原始资源的访问可能经由显式转换或隐式转换,一般而言显式转换比较安全,但隐式转换对客户比较方便。
也就是new和delete对应,new[] 和 delete[]对应。
. 主要是为了避免类型转换失败以及异常发生时对资源管理失效的麻烦。
例如,有一个表示处理程序优先级的函数和一个接受资源对象对优先级做处理的函数:
int priority();
void processWidget(std::tr1::shared_ptr pw,int p);
. 现在调用第二个函数的形式如下:
processWidget(new Widget,priority());
. 以上调用将无法通过编译,tr1::shared_ptr构造喊苏需要一个原始指针,且该函数是一个explicit的,无法进行隐式转换,因此需要直接写出:
processWidget(std::tr1::shared_ptr(new Widget),priority());
. 以上代码虽然能通过编译,但是还是存在资源泄露的问题,这是因为对编译器来说,产出一个processWidget的调用码前,必须先核算即将被传递的各个实参,因此对以上代码来说需要先做一下三步:
. 但是C++以什么次序来完成这三步呢,可以肯定的是new操作在调用构造函数前,但是调用priority函数则可能排在第一第二或第三的步。假设在第二步调用了priority函数,并且发生了异常,则new产生的指针将会被遗失,因为它尚未被置入智能指针内,这就可能导致资源泄露了。
避免这种问题的方法就是使用分离语句,将创建对象、创建指针和传参的步骤分开执行:
std::tr1::shared_ptr pw(new Widget);
processWidget(pw,priority());