Boost开发指南-3.2scoped_ptr

scoped_ptr

scoped_ptr是一个很类似auto_ptr/unique_ptr的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但scoped_ptr的所有权更加严格,不能转让,一旦scoped_ptr获取了对象的管理权,我们就无法再从它那里取回来。

scoped_ptr拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让。

类摘要

template<classT>
class scoped_ptr  //noncopyable
{   
private :
   T* px; //原始指针
   scoped_ptr (scoped_ptr const &); //拷贝构造函数私有化
   scoped_ptr & operator=(scoped_ptr const &);  //赋值操作私有化
   void operator==(scoped_ptr const&) const;  //相等操作私有化
   void operator!=(scoped_ptr const&) const;  //不等操作私有化

public:
   explicit scoped_ptr(T* p = 0); //显式构造函数
   ~scoped_ptr(); //析构函数
   void reset(T* p = 0); //重置智能指针
   T & operator*() const; //操作符重载
   T * operator->() const; //操作符重载
   T * get() const; //获得原始指针
   explicit operator bool() const; //显式bool值转型
   void swap(scoped_ptr & b); //交换指针
};
template<class T>inline //与空指针比较
bool operator==(scoped_ptr<T> const & p, boost::detail::sp_nullptr_t);

操作函数

scoped_ptr的构造函数接受一个类型为T*的指针 p,创建出一个scoped_ptr对象,并在内部保存指针参数p。p 必须是一个new表达式动态分配的结果,或者是个空指针(nullptr)。当 scoped_ptr 对象的生命期结束时,析构函数~scoped_ptr()会使用delete操作符自动销毁所保存的指针对象,从而正确地回收资源。

scoped_ptr同时把拷贝构造函数和赋值操作符都声明为私有的,禁止对智能指针的拷贝操作,保证了被它管理的指针不能被转让所有权。

成员函数reset()的功能是重置scoped_ptr:它删除原来保存的指针,再保存新的指针值p。如果p是空指针,那么scoped_ptr将不持有任何指针。一般情况下reset()不应该被调用,因为它违背了scoped_ptr的本意——资源应该一直由scoped_ptr自己自动管理。

scoped_ptr 用operator*()和 operator->()重载了解引用操作符*和箭头操作符->,以模仿被代理的原始指针的行为,因此可以把scoped_ptr对象如同指针一样使用。如果scoped_ptr保存的是空指针,那么这两个操作的行为未定义。

scoped_ptr提供了一个可以在bool语境中自动转换成bool值(如 if 的条件表达式)的功能,用来测试scoped_ptr是否持有一个有效的指针(非空)。它可以代替与空指针的比较操作,而且写法更简单。

成员函数swap()可以交换两个scoped_ptr保存的原始指针。它是高效的操作,被用于实现reset ()函数,也可以被boost::swap所利用。

成员函数get()返回scoped_ptr内部保存的原始指针,可以用在某些要求必须是原始指针的场景(如底层的C接口)。但使用时必须小心,这将使原始指针脱离scoped_ptr的控制!不能对这个指针做delete操作,否则 scoped_ptr析构时会对已经删除的指针再进行删除操作,发生未定义行为(通常是程序崩溃,这可能是最好的结果,因为它说明你的程序存在bug)。

最后,scoped_ptr支持有限的比较操作,不能在两个scoped_ptr之间进行相等或者不等测试,默认仅支持与C++11的nullptr进行比较(也可以是NULL或者0,因为这两者可以隐式转换为nullptr)。

用法

scoped_ptr 的用法很简单:在原本使用指针变量接受new表达式结果的地方改成用scoped_ptr对象,然后去掉哪些多余的try/catch和delete操作就可以了。像这样:

scoped_ptr<string> sp(new string("text")); //构造一个scoped_ptr对象

assert(sp); //使用显式bool转型
assert(sp != nullptr); //空指针比较操作

scoped_ptr是一种“智能指针”,因此其行为与普通指针基本相同,可以使用非常熟悉的*和->操作符:

cout << *sp << endl; //operator*取字符串的内容
cout << sp->size() << endl; //operator->取字符串的长度

但记住:不再需要delete操作,scoped_ptr会自动地帮助我们释放资源。如果我们对scoped_ptr执行delete会得到一个编译错误:因为scoped_ptr是一个行为类似指针的对象,而不是指针,对一个对象应用delete是不允许的。

scoped_ptr 把拷贝构造函数和赋值函数都声明为私有的,不允许拷贝或赋值,拒绝了指针所有权的转让,只能在scoped_ptr被声明的作用域内使用——除了scoped_ptr自己,其他任何人都无权访问被管理的指针,从而保证了指针的绝对安全。

scoped_ptr<string> sp2 = sp; //错误,scoped_ptr不能拷贝构造

如果代码编写者企图从一个 scoped_ptr构造或赋值另一个scoped_ptr,那么编译器会报出一个错误,阻止他这么做,从而保护了你的代码,而且是早在运行之前。scoped_ptr明确地表明了代码原始编写者的意图:只能在定义的作用域内使用,不可转让,这在代码后续的维护生命周期中很重要。由此也引出了另外一个结论:如果一个类持有scoped_ptr成员变量,那么它也会是不可拷贝和赋值的。例如:

class ptr_owned final //一个持有scoped_ptr成员的类
{                     //是不可拷贝和赋值的 
	scoped_ptr<int> m_ptr; //scoped_ptr成员
};
ptr_owned p; //类的一个实列
ptr_owned p2(p); //编译错误,不能拷贝构造

*->之外scoped_ptr没有定义其他操作符,所以不能对scoped_ptr进行++或者--等指针算术操作。与普通指针相比,它只有很小的接口,这一点使指针的使用更加安全,更容易使用同时更不容易被误用。下面的代码都是scoped_ptr的错误用法:

sp++; //错误,scoped_ptr未定义递增操作符
std::prev(sp); //错误,scoped_ptr未定义递减操作符

使用scoped_ptr会带来两个好处:一是使代码变得清晰简单,而简单意味着更少的错误;二是它并没有增加多余的操作,安全的同时保证了效率,可以获得与原始指针同样的速度。

struct posix_file //一个示范性质的文件类
{
	posix_file(const char* file_name) //构造函数打开文件
	{
		cout << "open file:" << file_name << endl; 
	}
	~posix_file() //析构函数关闭文件
	{
		cout << "close file" << endl;
	}
};

int main()
{
    //文件类的scoped_ptr,将在离开作用域时自动析构,从而关闭文件释放资源
	scoped_ptr<posix_file> fp(new posix_file("/tmp/a.txt"));

	scoped_ptr<int> p(new int); //一个int指针的scoped_ptr

	if (p) //在bool语境中测试指针是否有效
	{
		*p = 100; //可以像普通指针一样使用解引用操作符*
		cout << *p << endl;
	}

	p.reset(); //置空scoped_ptr,仅仅是演示

	assert(p == 0); //与0比较,p不持有任何指针
	if (!p) //在bool语境中测试,可以用!操作符
	{
		cout << "scoped_ptr == nullptr" << endl;
	} 

} //在这里发生scoped_ptr的析构,p和fp管理的指针自动被删除

对比unique_ptr

std::unique_ptr是在C++11标准中定义的新的智能指针,用来取代C++98中的std::auto_ptr。根据C++11标准,unique_ptr不仅能够代理new创建的单个对象,也能够代理new[]创建的数组对象,也就是说它结合了 scoped_ptr和scoped_array 两者的能力,在这里我们简单介绍它的单个对象用法。

template <class T, class D = default_delete<T>>   //使用删除器
class unique_ptr 
{
public:
   typedef some_define pointer; //内部类型定义
   typedef T element_type;
  
   constexpr unique_ptr() noexcept; //构造函数
   explicit unique_ptr(pointer p) noexcept;
   ~unique_ptr(); //析构函数
   unique_ptr& operator=(unique_ptr&& u) noexcept; //转移语义赋值
   element_type & operator*() const; //操作符重载
   pointer operator->() const noexcept; //操作符重载
   pointer get() const noexcept; //获得原始指针
   explicit operator bool() const noexcept; //bool值转型
   pointer release() noexcept; //释放指针的管理权
   void reset(pointer p) noexcept; //重置智能指针
   void swap(unique_ptr& u) noexcept; //交换指针
   unique_ptr(const unique_ptr&) = delete; //使用delete禁用拷贝
   unique_ptr& operator=(const unique_ptr&) = delete;
);
bool operator==(const unique_ptr& x, const unique_ptr& y);
...

unique_ptr的基本能力与scoped_ptr相同,同样可以在作用域内管理指针,也不允许拷贝构造和拷贝赋值”,例如:

unique_ptr<int> up(new int); //声明一个unique_ptr,管理int指针
assert(up); //bool语境测试指针是否有效
*up = 10; //使用operator*操作指针
cout << *up << endl;
up.reset(); //释放指针
assert(!up); //此时不管理任何指针

但unique_ptr要比 scoped_ptr有更多的功能:可以像原始指针一样进行比较,可以像shared_ptr 一样定制删除器,也可以安全地放入标准容器。因此,如果读者使用的编译器支持C++11标准,那么可以毫不犹豫地使用unique_ptr来代替scoped_ptr。

当然,scoped_ptr也有它的优点,“少就是多”永远是一句至理名言,它只专注于做好作用域内的指针管理工作,含义明确,而且不允许转让指针所有权。

但支持新的转移语义,如unique_ptr<int> up = std::move (another_ptr),解决了auto_ptr的在拷贝构造时微妙的转移语义问题。

make_unique

C++1l标准虽然定义了unique_ptr,但却“遗忘”了对应的工厂函数make_unique()(C++14标准补上了这个“漏洞”),于是 boost.smart_ptr库特意在头文件<boost/smart_ptr/make_unique.hpp>里实现了make_unique()函数,基本形式是:

template<class T, class. . . Args> //使用可变参数模板
inline typename boost::detail::up_if_not_array<T>::type
make_unique(Args&& . . . args) {  //使用可变参数模板
    return std::unique_ptr<T> (new T(...)); //C++11的完美转发
}

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

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

auto p = boost::make_unique<int>(10); //使用auto创建unique_ptr<int>对象
assert(p && *p == 10); //访问指针内容

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

代码示例

#include <iostream>
using namespace std;

#include <boost/smart_ptr.hpp>
#include <boost/smart_ptr/make_unique.hpp>
using namespace boost;

//

void case1()
{
	scoped_ptr<string> sp(new string("text"));

	assert(sp);
	assert(sp != nullptr);

	cout << *sp << endl;
	cout << sp->size() << endl;
}

//

struct posix_file
{
	posix_file(const char* file_name)
	{
		cout << "open file:" << file_name << endl;
	}
	~posix_file()
	{
		cout << "close file" << endl;
	}
};

void case2()
{
	scoped_ptr<posix_file> fp(new posix_file("/tmp/a.txt"));

	scoped_ptr<int> p(new int);

	if (p)
	{
		*p = 100;
		cout << *p << endl;
	}

	p.reset();

	assert(p == 0);
	if (!p)
	{
		cout << "scoped_ptr == nullptr" << endl;
	}

}

//

class ptr_owned final
{
	scoped_ptr<int> m_ptr;
};

void bad_case()
{
	scoped_ptr<string> sp(new string("text"));

	//sp++;
	//scoped_ptr<string> sp2 = sp;
	//std::prev(sp);
	//ptr_owned p;
	//ptr_owned p2(p);
}

//

void case_unique()
{
	auto p = boost::make_unique<int>(10);

	assert(p && *p == 10);

	p.release();
	assert(!p);

	auto a = boost::make_unique<int[]>(5);
	a[0] = 100;
	a[4] = 500;
	//a[5] = 1000;
}

//

int main()
{
	case1();
	case2();
	case_unique();
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值