最先讲的就是指针,这是C语言中,不少程序员害怕的东西,害怕的原因大多是因为不了解其初始化、调用、赋值和清除的方式,而智能指针则可以去除这个顾虑,在初始化时就已经预定了删除,排解了后顾之忧。1998年修订的第一版C++标准只提供了一种智能指针:std::auto_ptr
,它基本上就像是个普通的指针:通过地址来访问一个动态分配的对象。std::auto_ptr
之所以被看作是智能指针,是因为它会在析构的时候调用delete
操作符来自动释放所包含的对象。当然这要求在初始化的时候,传给它一个由new
操作符返回的对象的地址。既然std::auto_ptr
的析构函数会调用delete
操作符,它所包含的对象的内存会确保释放掉。这是智能指针的一个优点。
当尝试和异常联系起来时这就更加重要了:没有std::auto_ptr
这样的智能指针,每一个动态分配内存的函数都需要捕捉所有可能的异常,以确保在异常传递给函数的调用者之前将内存释放掉。Boost C++ 库的智能指针系列提供了许多可以用在各种场合的智能指针。
二、RAII
先介绍下一个专业词汇:RAII(Resource Application Immediately Initialize)资源申请即初始化。这也是智能指针的基本原理,智能指针只是这个习语的其中一例。智能指针确保在任何情况下,动态分配的内存都能得到正确释放,从而将开发人员从这项任务中解放了出来。 这包括程序因为异常而中断,原本用于释放内存的代码被跳过的场景。用一个动态分配的对象的地址来初始化智能指针,在析构的时候释放内存,就确保了这一点。因为析构函数总是会被执行的,这样所包含的内存也将总是会被释放。
无论何时,一定得有第二条指令来释放之前另一条指令所分配的资源时,RAII 都是适用的。许多的 C++ 应用程序都需要动态管理内存,因而智能指针是一种很重要的 RAII 类型。不过 RAII 本身是适用于许多其它场景的。
下面例子中的这个类就利用了这样的机制:
#include <windows.h>
class windows_handle
{
public:
windows_handle(HANDLE h)
: handle_(h)
{
}
~windows_handle()
{
CloseHandle(handle_);
}
HANDLE handle() const
{
return handle_;
}
private:
HANDLE handle_;
};
int main()
{
windows_handle h(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId()));
SetPriorityClass(h.handle(), HIGH_PRIORITY_CLASS);
}
它定义了一个名为windows_handle的类,其析构函数调用了CloseHandle()函数。这是一个Windows API函数,因而这个程序只能在Windows上运行。在Windows上,许多资源在使用之前都要求打开,这意味着资源不再使用之后就应该关闭。windows_handle 类的机制能确保这一点。
它的实例h是以一个句柄来初始化。例子中的OpenProcess函数没有什实际意义,就是帮助给类的实例赋值并使用的。重点是通过OpenProcess打开的资源不需要显示的调用 CloseHandle来关闭。当然,应用程序终止时资源也会随之关闭。然而,在更加复杂的应用程序里,windows_handle类确保当一个资源不再使用时就能正确的关闭。某个资源一旦离开了它的作用域(上例中 h 的作用域在main函数的末尾)它的析构函数会被自动的调用,相应的资源也就释放掉了。这也就是RAII的精髓所在。
三、Boost的智能指针
3.1作用域指针
一个作用域指针独占一个动态分配的对象。对应的类名为boost::scoped_ptr,它的定义在boost/scoped_ptr.hpp中。不像std::auto_ptr,一个作用域指针不能传递它所包含的对象的所有权到另一个作用域指针。一旦用一个地址来初始化,这个动态分配的对象将在析构阶段释放。因为一个作用域指针只是简单保存和独占一个内存地址,所以boost::scoped_ptr的实现就要比std::auto_ptr简单。在不需要所有权传递的时候应该优先使用boost::scoped_ptr。在这些情况下,比起std::auto_ptr它是一个更好的选择,因为可以避免不经意间的所有权传递。下面这个例子简单描述了scope_ptr的使用方法:
#include <boost/scoped_ptr.hpp>
int main()
{
boost::scoped_ptr<int> i(new int);
*i = 1;
*i.get() = 2;
i.reset(new int);
}
一经初始化,智能指针boost::scoped_ptr所包含的对象可以通过类似于普通指针的接口来访问。这是因为重载了相关的操作符operator*(),operator->() 和 operator bool()。此外,还有get和reset方法。前者返回所含对象的地址,后者用一个新的对象来重新初始化智能指针。在这种情况下,新创建的对象赋值之前会先自动释放所包含的对象。
boost::scoped_ptr的析构函数中使用delete操作符来释放所包含的对象。这对 boost::scoped_ptr所包含的类型加上了一条重要的限制。它不能用动态分配的数组来做初始化,因为这需要调用delete[]来释放。在这种情况下,可以使用下面将要介绍的 boost:scoped_array类。
3.2、作用域数组
作用域数组的使用方式与作用域指针相似。关键不同在于,作用域数组的析构函数使用 delete[]操作符来释放所包含的对象。因为该操作符只能用于数组对象,所以作用域数组必须通过动态分配的数组来初始化。对应的作用域数组类名为boost::scoped_array,它的定义在boost/scoped_array.hpp 里。例子和上面类似,只是操作对象成了数组:
#include <boost/scoped_array.hpp>
int main()
{
boost::scoped_array<int> i(new int[2]);
*i.get() = 1;
i[1] = 2;
i.reset(new int[3]);
}
boost:scoped_array类重载了操作符operator[]和operator bool。可以通过operator[]操作符访问数组中特定的元素,于是boost::scoped_array类型对象的行为就酷似它所含的数组。正如boost::scoped_ptr那样,boost:scoped_array也提供了get和reset方法,用来返回和重新初始化所含对象的地址。
3.3、共享指针
这是使用率最高的智能指针,但是 C++ 标准的第一版中缺少这种指针。它已经作为技术报告1(TR 1)的一部分被添加到标准里了。如果开发环境支持的话,可以使用 memory 中定义的 std::shared_ptr。在Boost C++库里,这个智能指针命名为boost::shared_ptr,定义在boost/shared_ptr.hpp里。
智能指针boost::shared_ptr基本上类似于boost::scoped_ptr。关键不同之处在于 boost::shared_ptr 不一定要独占一个对象。它可以和其他boost::shared_ptr类型的智能指针共享所有权。 在这种情况下,当引用对象的最后一个智能指针销毁后,对象才会被释放。因为所有权可以在boost::shared_ptr之间共享,任何一个共享指针都可以被复制,这跟 boost::scoped_ptr是不同的。这样就可以在标准容器里存储智能指针了——你不能在标准容器中存储std::auto_ptr,因为它们在拷贝的时候传递了所有权。
因为 boost::shared_ptr 能够共享它所含对象的所有权,所以保存在容器中的拷贝(包括容器在需要时额外创建的拷贝)都是和原件相同的。如前所述,std::auto_ptr做不到这一点,所以绝对不应该在容器中保存它们。正是有了它,我们才可以在标准容器中安全的使用动态分配的对象:
#include <boost/shared_ptr.hpp>
#include <vector>
int main()
{
std::vector<boost::shared_ptr<int> > v;
v.push_back(boost::shared_ptr<int>(new int(1)));
v.push_back(boost::shared_ptr<int>(new int(2)));
boost::shared_ptr<int> i1(new int(1));
boost::shared_ptr<int> i2(i1);
i1.reset(new int(2));
}
类似于boost::scoped_ptr、boost::shared_ptr类重载了以下这些操作符:operator*、operator-> 和operator bool,另外还有get和reset函数来获取和重新初始化所包含的对象的地址。上例中定义的2个共享指针i1和i2都引用到同一个int类型的对象。i1通过new操作符返回的地址显示的初始化,i2通过i1拷贝构造而来。i1接着调用reset,它所包含的整数的地址被重新初始化,不过它之前所包含的对象并没有被释放,因为i2仍然引用着它。智能指针 boost::shared_ptr记录了有多少个共享指针在引用同一个对象,只有在最后一个共享指针销毁时才会释放这个对象。
默认情况下,boost::shared_ptr 使用delete操作符来销毁所含的对象。然而,具体通过什么方法来销毁是可以指定的,就像下面的例子:
#include <boost/shared_ptr.hpp>
#include <windows.h>
int main()
{
boost::shared_ptr<void> h(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId()), CloseHandle);
SetPriorityClass(h.get(), HIGH_PRIORITY_CLASS);
}
共享指针的构造函数第二个参数是一个函数对象CloseHandle,当h超出其作用域时,将不再调用delete了。
3.4、共享数组
共享数组的行为类似于共享指针,方式则类似scoped_array,在此不多赘述。