scoped_ptr

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自己,其他任何人都无权访问被管理的指针,从而保证了指针的绝对安全.
 

猜你喜欢

转载自blog.csdn.net/Edidaughter/article/details/121881620