Boost程序库完全开发指南:深入C++”准”标准库(第5版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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操作符自动销毁所保存的指针对象,从而正确地回收资源实际上调用的是boost::check_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内部使用了BOOST_ASSERT来断言指针非空,但它仅工作在debug模式下。

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()函数,其基本形式如下实际上make_unique.hpp里有两个头文件,使用模板元编程技术分别创建单个对象和数组对象。

需要注意两点:其一,它不含在头文件<boost/smart_ptr.hpp>里,必须单独包含;其二,它位于名字空间boost而不是std,这是为了避免潜在的冲突。

boost::make_unique()的用法与C++14标准是一样的,示范代码如下:

scoped_ptr不需要也不可能有make_scoped()函数,因为它不能拷贝也不能转移。