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管理的指针自动被删除
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
,解决了auto_ptr的在拷贝构造时微妙的转移语义问题。
C++1l标准虽然定义了unique_ptr,但却“遗忘”了对应的工厂函数make_unique()(C++14标准补上了这个“漏洞”),于是 boost.smart_ptr库特意在头文件
里实现了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::make_unique()的用法与C++14标准是一样的,示范代码如下:
auto p = boost::make_unique<int>(10); //使用auto创建unique_ptr对象
assert(p && *p == 10); //访问指针内容
scoped_ptr不需要也不可能有make_scoped()函数,因为它不能拷贝不能转移。
#include
using namespace std;
#include
#include
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 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();
}