1.初识scoped_ptr
scoped_ptr是一个与auto_ptr/unique_ptr很类似的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但scoped_ptr的所有权更加严格,不能转让,一旦scoped_ptr获取了对象的管理权,我们就无法再从它那里收回来。scoped_ptr拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让。
2.类摘要
template<class T>
class scoped_ptr
{
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);
3.一个简单的模拟实现与简单应用
#include <iostream>
#include <string>
using namespace std;
template<class T>
class scoped_ptr
{
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):px(p)
{
}
// 析构函数
~scoped_ptr()
{
delete px;
px = nullptr;
}
// 重置智能指针的写法
void reset_1(T * p = 0)
{
if(p!=px && p!= nullptr)
{
delete px;
px = p;
}
}
typedef scoped_ptr<T> this_type;
void reset_2(T* p = 0)
{
this_type(p).swap(*this);
}
// 解引用操作符重载
T& operator*() const
{
return *px;
}
// 箭头操作符重载
T* operator->() const
{
return px;
}
// 获得原始指针
T* get() const;
// 显示bool值的转换
explicit operator bool() const;
// 交换指针
void swap(scoped_ptr & b)
{
T* tmp = b.px;
b.px = px;
px = tmp;
}
};
//template<class T> inline
//bool operator==(scoped_ptr<T> const & p,boost::detail::sp_nullptr_t);
class Test
{
public:
void fun()
{
std::cout <<"line = " << __LINE__ << ", This is Test fun()" << endl;
}
};
int main()
{
std::cout <<"line = " << __LINE__<< ". This is a tiny scoped_ptr implementation simulation program ." << std::endl;
int* p = new int(10);
scoped_ptr<int> ps(p);
std::cout <<"line = " << __LINE__<< ", *ps is " << *ps << endl;
scoped_ptr<Test> ps1(new Test);
ps1->fun();
// scoped_ptr<Test> ps2 = ps1; // 不允许拷贝构造,编译时期就会报错并提示不允许这样的操作
int *q = new int(100);
ps.reset_1(q);
std::cout <<"line = " << __LINE__<< ", *ps is " << *ps << endl;
int *r = new int(1000);
ps.reset_2(r);
std::cout <<"line = " << __LINE__<< ", *ps is " << *ps << endl;
return 0;
}
4.比较reset_1和reset_2的写法
// 重置智能指针的写法1
void reset_1(T * p = 0)
{
if(p!=px && p!= nullptr)
{
delete px;
px = p;
}
}
// 重置智能指针的写法2 typedef scoped_ptr<T> this_type; void reset_2(T* p = 0) { this_type(p).swap(*this); // this_type(p)是创建的临时的无名的局部对象,一旦脱离函数空间,对象就会调用析构函数从而被释放 }// 交换指针 void swap(scoped_ptr & b) { T* tmp = b.px; b.px = px; px = tmp; }
5.boost源码中的【type_must_be_complete】
template<class T> inline void checked_delete(T* x)
{
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
(void) sizeof(type_must_be_complete);
delete x;
}
函数总共三行语句,第三行是根本目的,很容易理解,前两行的目的就是为了所谓的安全性了。怎么个安全法呢?这两个函数是函数模版,在编译时无法确定参数的类型,而动态运行时的错误是比较棘手的,所以用这行代码:
typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
来将运行时错误转变为编译期错误。这句话其实就是定义了一个固定大小的char型数组,数组名为type_must_be_complete,数组大小是多少呢?是sizeof(T)?1:-1, ?:这个三元操作符大家都很熟悉了,若sizeof(T)非0,这个表达式的值为1,即typedef了一个大小为1的char型数组,否则定义一个大小为-1的数组。数组大小还能为负数?当然不能,于是就会报错,而且是编译期错误,于是就将一个动态运行时错误在编译时就发现了。
接下来解释sizeof什么时候返回0.
C/C++语言本身似乎没有这种情况,但有些编译器会作一些扩展,比如GCC对于incomplete type使用sizeof时,会返回0.那什么又叫做incomplete type呢,就是那些声明了,但没有定义的类型,例如:
class A;
extern A a;
C++标准允许通过一个 delete 表达式删除指向不完全类的指针。如果该类有一个非平凡的析构函数,或者有一个类相关的 delete 操作符,那么其行为就是无定义的。因此编译器作了这种扩展,以将这种未定义的行为转为编译期错误,帮助程序员们及早发现。
函数的第二行语句的作用据说是为了防止编译器优化,因为编译器检测到typedef的类型未被使用到的话可能就会将其优化掉,因而第二行语句使用了这个类型,告诉编译器这个typedef是有用的,不能优化掉。至于(void)强制类型转换,是为了消除编译器对未使用sizeof返回值的警告。
6.与标准库中的unique_ptr作比较
unique_ptr是在C++标准中定义的新的智能指针,用来取代曾经的auto_ptr. 根据C++标准定义(C++11.20.7.1),unique_ptr不仅能够代理new创建的单个对象,也能够代理new[ ]创建的数组对象.
unique_ptr的基本能力与scoped_ptr相同,同样可以在作用域内管理指针,也不允许拷贝构造和拷贝赋值,但unique_ptr要比scoped ptr有更多的功能:可以像原始指针一样进行比较,可以像shared_ptr一样定制删除器,也可以安全地放入标准容器. 因此,如果读者使用的编译器支持C++11标准,那么可以毫不犹豫地使用unique_ptr来代替scoped_ptr.
当然,scoped_ptr也有它的优点,“少就是多”永远是一句至理名言,它只专注于做好作用域内的指针管理工作,含义明确,而且不允许转让指针所有权.
7.与标准库中的auto_ptr作比较
scoped_ptr的用法与auto_ptr几乎一样,大多数情况下它可以与auto_ptr相互替换,它也可以从一个auto_ptr获得指针的管理权(同时auto_ptr失去管理权).
scoped_ptr也具有auto_ptr同样的“缺陷”——不能用作容器的元素,但原因不同:auto_ptr是因为它的转移语义,而scoped_ptr则是因为不支持拷贝和赋值,不符合容器对元素类型的要求.
scoped_ptr与auto_ptr的根本性区别在于指针的所有权. auto_ptr特意被设计为指针的所有权是可转移的,可以在函数之间传递,同一时刻只能有一个auto_ptr管理指针. 它的用意是好的,但转移语义太过于微妙,不熟悉auto_ptr 特性的初学者很容易误用引发错误. 而scoped_ptr把拷贝构造函数和赋值函数都声明为私有的,拒绝了指针所有权的转让----除了scoped_ptr自己,其他任何人都无权访问被管理的指针,从而保证了指针的绝对安全.