scoped_ptr源码剖析

scoped_ptr是一个很类似auto_ptr的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但scoped_ptr的所有权更加严格,不能转让,一旦scoped_ptr获取了对象的管理权,你就无法再从它那里取回来(reset函数是重置,会清空自己,同样无法取回)。

源码剖析

源码如下:

namespace boost
{

//  scoped_ptr mimics a built-in pointer except that it guarantees deletion
//  of the object pointed to, either on destruction of the scoped_ptr or via
//  an explicit reset(). scoped_ptr is a simple solution for simple needs;
//  use shared_ptr or std::auto_ptr if your needs are more complex.

template<class T> class scoped_ptr // noncopyable
{
private:

    T * px;     

    //禁止copy assignment
    scoped_ptr(scoped_ptr const &);
    scoped_ptr & operator=(scoped_ptr const &);

    typedef scoped_ptr<T> this_type;

    //禁止比较
    void operator==( scoped_ptr const& ) const;
    void operator!=( scoped_ptr const& ) const;

public:

    typedef T element_type;
    //使用普通指针构造,禁止隐式转换
    explicit scoped_ptr( T * p = 0 ): px( p ) // never throws
    {
    }

#ifndef BOOST_NO_AUTO_PTR
    //噗,scoped_ptr是可以从auto_ptr那里夺取过来的,不过auto_ptr调用了releadse自然就失效了
    explicit scoped_ptr( std::auto_ptr<T> p ) BOOST_NOEXCEPT : px( p.release() )
    {
    }
#endif

    ~scoped_ptr() // never throws
    {
        boost::checked_delete( px );   //利用sizeof(T)的大小去声明一个数组,检查是否可行,不可行说明是incomplete类型,不执行delete,这在编译器就可以决断
    }

    //使用该函数删除scoped_ptr内部指针,重置为p指针。不过这不符合scoped_ptr意图,该函数尽量不要用
    void reset(T * p = 0) // never throws
    {
        BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
        this_type(p).swap(*this);
    }

    T & operator*() const // never throws
    {
        BOOST_ASSERT( px != 0 );
        return *px;
    }

    T * operator->() const // never throws
    {
        BOOST_ASSERT( px != 0 );
        return px;
    }

    T * get() const BOOST_NOEXCEPT
    {
        return px;
    }

// implicit conversion to "bool"
#include <boost/smart_ptr/detail/operator_bool.hpp>  //有关bool类型的运算符重载

    //swap仅交换指针,这是对"pimpl"手法的优化
    void swap(scoped_ptr & b) BOOST_NOEXCEPT
    {
        T * tmp = b.px;
        b.px = px;
        px = tmp;
    }
};

//boost作用域内的swap,是一个no-member函数,供外部交换两个scoped_ptr。不用std::swap的原因是std::swap不能优化"pimpl"手法。
template<class T> inline void swap(scoped_ptr<T> & a, scoped_ptr<T> & b) BOOST_NOEXCEPT
{
    a.swap(b);
}

// get_pointer(p) is a generic way to say p.get()
//提供外部获取指针的途径
template<class T> inline T * get_pointer(scoped_ptr<T> const & p) BOOST_NOEXCEPT
{
    return p.get();
}
} // namespace boost

析构函数中的check_delete是这样的:

template<class T> inline void checked_delete(T * x)
{
    // intentionally complex - simplification causes regressions
    typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
    (void) sizeof(type_must_be_complete);
    delete x;
}

其实就是按T的大小声明一个数组,如果该类型为不完全(incomplete)类型,这样编译会不通过。如仅仅声明一个类,但没有实现,直接用来做参数,这就是不完全类型。

关于operator bool的重载如下:

 operator bool () const BOOST_NOEXCEPT
    {
        return px != 0;
    }

这样可以把该指针做来一个bool值来判断。注意,operator bool重载不能有返回值。

操作函数

scoped_ptr的构造函数接受一个类型为T*的指针p,创建出一个scoped_ptr对象,并在内部保存指针参数p。p必须是一个new表达式动态分配的结果,或者是个空指针。当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不支持比较操作,不能在两个scoped_ptr之间、scoped_ptr和原始指针或空指针之间进行相等或者不相等测试,我们也无法为它编写额外的比较函数,因为它已经将operator==和operator!=两个操作符重载都声明为私有的。**但scoped_ptr提供了一个可以在bool语境中自动转换成bool值(如if条件表达式)的功能。用来测试scoped_ptr是否持有一个有效的指针(非空)。它可以代替与空指针的比较操作,而且写法更简单。

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

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

用法

下面是代码示例:

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<int> p(new int);
    if(p){
        *p = 100;
        cout<<*p<<endl;
    }   
    p.reset();
    assert(p == 0); 
    if(!p){
        cout<<"scoped_ptr == null"<<endl; 
    }   

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

    return 0;
}

这将输出:

输出

注意:scoped_ptr可没有定义operator ++,等操作,不要乱用。并且scoped_ptr在作为成员变量时,所谓的作用域即类的内部作用域,所以会在析构函数的最后scoped_ptr才会析构。

与auto_ptr的区别

之前的这篇博客分析了auto_ptr:auto_ptr源码分析,这次来说说scoped_ptr和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自己,其他任何人都无权访问被管理的指针,从而保证了指针的绝对安全。

比起auto_ptr,scoped_ptr更明确表明了代码原始编写者的意图:只能在定义的作用域内使用,不可转让,这在代码后续的维护生命周期中很重要。


参考:

  • Boost程序库完全开发指南,作者:罗剑锋
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值