3.2 scoped_ptr
scoped_ptr是一个与auto_ptr/unique_ptr很类似的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但scoped_ptr的所有权更加严格,不能转让,一旦scoped_ptr获取了对象的管理权,我们就无法再从它那里收回来。
scoped_ptr拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让。
3.2.1 类摘要
scoped_ptr的类摘要如下:
3.2.2 操作函数
scoped_ptr的构造函数接收一个类型为T*的指针p,创建出一个scoped_ptr对象,并在内部保存指针p。p必须是一个new表达式动态分配的结果,或者是一个空指针(nullptr)。当scoped_ptr对象的生命周期结束时,析构函数会使用delete操作符自动销毁所保存的指针对象,从而正确地回收资源。
scoped_ptr同时把拷贝构造函数和赋值操作符都声明为私有的,禁止对智能指针进行拷贝操作(其原理可参考4.1节的noncopyable),保证了被它管理的指针的所有权不能被转让。
成员函数reset()的功能是重置scoped_ptr:,它删除原来保存的指针,再保存新的指针值p。如果p是空指针,那么scoped_ptr将不持有任何指针。在一般情况下,reset()不应该被调用,因为它违背了scoped_ptr的本意——资源应该一直由scoped_ptr自动管理。
scoped_ptr用operator*()和operator->()重载了解引用操作符“*”和箭头操作符“->”,以模仿被代理的原始指针的行为,因此可以如同使用指针一样使用scoped_ptr对象。如果scoped_ptr保存的是空指针,那么这两个操作的行为未定义。
scoped_ptr提供了一个可以在bool语境(如if条件表达式)中自动转换成bool值的功能,用来测试scoped_ptr是否持有一个有效的指针(非空)。它可以代替与空指针的比较操作,而且其写法更简单。
成员函数swap()可以交换两个scoped_ptr保存的原始指针,此操作很高效,可以用于实现reset()函数,也可以被std::swap使用。
成员函数get()返回scoped_ptr内部保存的原始指针,可以用在某些要求必须是原始指针的场景(如底层的C接口)。但在使用函数get()时必须小心,这将使原始指针脱离scoped_ptr的控制,不能对这个指针进行delete操作,否则在scoped_ptr析构时会对已经删除的指针再次进行删除操作,发生未定义行为。
scoped_ptr支持有限的比较操作,不能在两个scoped_ptr之间进行相等或不等测试,默认它仅支持与nullptr进行比较(也可以是NULL或0,因为这两者可以隐式转换为nullptr)。
3.2.3 用法
scoped_ptr的用法很简单:在原本使用指针变量接收new表达式结果的地方改成用scoped_ptr对象接收new表达式结果,然后去掉那些多余的try/catch和delete操作即可。例如:
scoped_ptr<string>sp(new string("text"));//构造一个scoped_ptr对象
scoped_ptr是一种智能指针,因此其行为与普通指针基本相同,可以使用非常熟悉的“*”“->”操作符:
但记住:不再需要进行delete操作,scoped_ptr会自动帮我们释放资源。如果我们对scoped_ptr执行delete操作,会得到一个编译错误。因为scoped_ptr是一个行为类似指针的对象实例,而不是指针,所以不允许对一个对象应用delete操作。
scoped_ptr把拷贝构造函数和赋值函数都声明为私有的,不允许拷贝或赋值,拒绝转让指针的所有权,只能在scoped_ptr被声明的作用域内使用(除了scoped_ptr自己,其他任何人都无权访问被管理的指针),从而保证了指针的绝对安全。
如果代码编写者企图从一个scoped_ptr构造或赋值另一个scoped_ptr,那么编译器会报错,阻止他这么做,从而保护了我们的代码,而且这一行为发生在运行代码之前。scoped_ptr明确地表明了代码原始编写者的意图:只能在定义的作用域内使用,不可转让,这在代码后续的维护生命周期中很重要。
由此也引出了另外一个结论:如果一个类持有scoped_ptr成员变量,那么它也会是不可拷贝和赋值的。例如:
除“*”“->”之外,scoped_ptr没有定义其他操作符,所以不能对scoped_ptr进行“++”“--”等指针算术操作。与普通指针相比,它只有很小的接口,这一点使得使用指针更加安全、更容易且不容易被误用。下面的代码都是scoped_ptr的错误用法:
使用scoped_ptr会带来两个好处:一是使代码变得清晰简单,而简单意味着更少的错误;二是它并没有增加多余的操作,安全的同时保证了效率,可以获得与原始指针同样的速度。
示范scoped_ptr用法的另一段代码如下:
程序运行结果如下:
3.2.4 对比标准
unique_ptr是在C++标准中定义的新的智能指针,用来取代曾经的auto_ptr。根据C++标准定义(C++11.20.7.1),unique_ptr不仅能够代理new创建的单个对象,也能够代理new[]创建的数组对象,在这里我们简单介绍它对单个对象的用法。
1.unique_ptr
C++标准中对unique_ptr的定义如下(进行了适当简化):
unique_ptr的基本能力与scoped_ptr相同,同样可以在作用域内管理指针,也不允许拷贝构造和拷贝赋值(但支持转移语义)。例如:
但unique_ptr要比scoped_ptr有更多的功能:unique_ptr可以像原始指针一样进行比较,可以像shared_ptr一样定制删除器,也可以安全地放入标准容器。因此,如果读者使用的编译器支持C++11标准,那么可以毫不犹豫地使用unique_ptr来代替scoped_ptr。
当然,scoped_ptr也有它的优点,它只专注于做好作用域内的指针管理工作,含义明确,而且不允许转让指针的所有权。
2.make_unique
C++11标准虽然定义了unique_ptr,但却“遗忘”了对应的工厂函数make_unique()(C++14标准补上了这个漏洞),于是boost.smart_ptr库特意在头文件<boost/smart_ptr/make_unique.hpp>里实现了make_unique()函数,其基本形式如下:
需要注意两点:其一,它不含在头文件<boost/smart_ptr.hpp>里,必须单独包含;其二,它位于名字空间boost而不是std,这是为了避免潜在的冲突。
boost::make_unique()的用法与C++14标准是一样的,示范代码如下:
scoped_ptr不需要也不可能有make_scoped()函数,因为它不能拷贝也不能转移。