C++11 标准库源代码剖析:连载之四

Smart Pointers

Smart pointer,也就是所谓的“智能指针”,是指那些能够自我管理生命周期的指针对象。C++ 11之前,标准库只提供了一种智能指针,即std::auto_ptr。不过std::auto_ptr用处有限,大家更多地是使用boost智能指针。随着时间推移,智能指针正在变得越来越流行,越来越重要,所以标准委员会在制定C++ 11标准时,将boost智能指针也纳入到标准中。

C++ 11标准库提供了四种智能指针:

  • unique_ptr
  • shared_ptr
  • weak_ptr
  • auto_ptr

auto_ptr因为功能有限,用处也有限,所以就不讲了。至于weak_ptr,它的实现方式和shared_ptr很像,甚至大部分代码是通用的,所以也不专门讲了。下面我们重点分析一下unique_ptrshared_ptr

unique_ptr

顾名思义,unique_ptr就是一个原生指针独一无二的拥有者和管理者。当一个unique_ptr离开其作用域时,其管理的原生指针会被自动销毁。

标准库中定义了两种类型的unique_ptr

  1. unique_ptr,管理通过new获得的原生指针。
  2. unique_ptr,管理通过new[]获得的指针数组。

这两种智能指针的实现方式大同小异,我们主要分析unique_ptr的源码。理解了unique_ptr的实现原理,unique_ptr的实现原理自然也就明了了。

unique_ptr的源代码

// file: 

template >
class unique_ptr {
public:
    typedef _Tp element_type;
    typedef _Dp deleter_type;
    typedef typename __pointer_type<_Tp, deleter_type>::type pointer;

private:
    __compressed_pair __ptr_;

    // ...
};

unique_ptr的声明包含两个模板参数,第一个参数_Tp显然就是原生指针的类型。第二个模板参数_Dp是一个deleter,默认值为default_delete<_Tp>default_delete是一个针对delete operator的函数对象:

// file: memory

template
struct default_delete {
    void operator()(T* ptr) const noexcept {
        delete ptr;
    }
};

注意这行代码:

typedef typename __pointer_type<_Tp, deleter_type>::type pointer;

__pointer_type是一个type trait,用来“萃取”出正确的指针类型。为了方便理解,大可以认为它和下面的代码是等价的:

typedef _Tp* pointer;

unique_ptr内部用__compressed_pair保存数据,__compressed_pair是一个“空基类优化”的pair,阅读源代码时,完全可以将它当做一个std::pair来对待。

这基本就是unique_ptr的全部声明,下面我们来看如何构造一个unique_ptr

// file: memory

template >
class unique_ptr {
    // ...

public:
    // 默认构造函数,调用pointer的默认构造函数
    inline constexpr unique_ptr() noexcept {
        : __ptr_(pointer()) {
    }

    // 将一个nullptr转换为一个unique_ptr
    inline constexpr unique_ptr(nullptr_t) noexcept 
        : __ptr_(pointer()) {
    }

    // 拷贝构造函数,注意参数类型为pointer,而不是const point&
    inline explicit unique_ptr(pointer __p) noexcept 
        : __ptr_(std::move(__p)){ 
    }

    // 移动构造函数
    inline unique_ptr(__u.get_deleter())) {
    }

    // 移动赋值
    inline unique_ptr& operator=(unique_ptr&& __u) noexcept {
        reset(__u.release());
        __ptr_.second() = std::forward(__u.get_deleter());
        return *this;
    }

    inline ~unique_ptr(){reset();}

    // ...
};

unqiue_ptr还定义了两个很重要的函数:reset(pointer)release()reset(pointer)的功能是用一个新指针替换原来的指针,而release()则是是放弃原生指针的所有权。

// file: memory

template >
class unique_ptr {
    // ...

public:

    // 放弃对原生指针的所有权,并返回原生指针
    inline pointer release() noexcept {
        pointer __t = __ptr_.first();
        __ptr_.first() = pointer();
        return __t;
    }

    // 用__p替换原生指针,被替换的指针最终被销毁
    inline void reset(pointer __p = pointer()) noexcept {
        pointer __tmp = __ptr_.first();
        __ptr_.first() = __p;
        if (__tmp)
            __ptr_.second()(__tmp);
    }

到目前为止,unique_ptr还不像个指针,因为还缺少两个方法:operator*operator->

// file: memory

template >
class unique_ptr {
    // ...

public:
    inline add_lvalue_reference<_Tp>::type operator*() const {
        return *__ptr_.first();
    }

    inline pointer operator->() const noexcept {
        return __ptr_.first();
    }

这几乎就是unique_ptr的全部源代码了,总的来说比较容易理解。下面我们来分析一个稍微复杂一些的智能指针:shared_ptr

shared_ptr

还是先从声明入手:

// file: memory

template
class shared_ptr {
public:
    typedef _Tp element_type;

private:
    element_type *__ptr_;
    __shared_weak_count* __cntrl_;

    // ...
};

shared_ptr内部维护了两个指针:一个是被其管理原生指针__ptr_,还有一个类型为__shared_weak_count的指针__cntrl_。那么这个__shared_weak_count又是什么呢?

file: memory

class __shared_count {
    // not copy constructible and not assignable
    __shared_count(const __shared_count&);
    __shared_count& operator=(const __shared_count&);

protected:
    long __shared_owners_; // how many owners do I have?
    virtual ~__shared_count();

public:
    explicit __shared_count(long __refs = 0) noexcept 
        : __shared_owners(__refs){}

        void __add_shared() noexcept;
        bool __release_shared() noexcept;
};

class __shared_weak_count : private __shared_count {
    long __shared_weak_owners_;

public:
    explicit __shared_weak_count(long __refs = 0) noexcept {
        : __shared_count(__refs), 
          __shared_weak_owners(__refs) {}

protected:
    virtual ~__shared_weak_count();

public:
    void __add_shared() noexcept;
    void __add_weak() noexcept;
    void __release_shared() noexcept;
    void __release_weak() noexcept;
    long use_count() const noexcept { return __shared_count::use_count();}

private:
    virtual void __on_zero_shared_weak() noexcept = 0;
};

__shared_weak_count是个虚基类,从它声明的类成员可以看出,这个类的作用应该是管理引用计数。实际上,shared_ptrweak_ptr内部都声明了__shared_weak_count*类型的成员变量,也就是说,__shared_weak_count同时管理shared ownershared weak owner,我个人认为这种做法值得商榷。

虚基类的作用类似于接口,是没法直接使用的,所以还必须定义一个“实在”类:

// file: memory

template
class __shared_ptr_pointer : public __shared_weak_count {
    __compressed_pair<__compressed_pair<_Tp, _Dp>, _Alloc> __data_;

public:
    inline __shared_ptr_pointer(_Tp __P, _Dp __d, _Alloc __a)
        : __data_(__compressed_pair<_Tp, _Dp>(__p, std::move(__d)), std::move(__a)) {}

    // ...

};

后面我们会看到,__shared_ptr_pointer正是shared_ptr的大内总管,不仅要记录shared_ptrshared owner,还要负责分配内存和销毁指针等工作。所以__shared_ptr_pointer类实际上有三个成员:

long __shared_owners_;
long __shared_weak_owners_;
__compressed_pair<__compressed_pair<_Tp, _Dp>, _Alloc> __data_;

理解了__shatrf_ptr_pointershared_ptr的源代码就容易读了:

// file: memory

template>
class shared_ptr {
public:
    typedef _Tp element_type;

private:
    element_type*           __ptr_;
    __shared_weak_count*    __cntrl_;

    struct __nat{int __for_bool_;}; // placeholder

    // ...
};


// 如果 Yp* 能够转换成 _Tp*,则可以由 _Yp* 构造一个shared_ptr<_Tp>
template
template
shared_ptr<_Tp>::shared_ptr(_Yp* __p,
        typename enable_if::value, __nat>::type)
        : __ptr_(__p) {
        unique_ptr<_Yp> __hold(__p);
        typedef __shared_ptr_pointer<_Yp*, default_delete<_Yp>, allocator<_Yp> >_CntrBlk;
        __cntrl_ = new _CntrBlk(__p, default_delete<_Yp>(), allocator<_Yp>());
        __hold.release();
}

// copy constructor, increment reference count
template
inline shared_ptr::shared_ptr(const shared_ptr& __r) noexcept
    : __ptr(__r.__ptr_), __cntrl_(__r.__cntrl_) {
    if (__cntrl_)
        __cntrl_->__add_shared();
}

// move constructor, does't increment reference count
template
inline shared_ptr::shared_ptr(shared_ptr&& __r) noexcept
    : __ptr_(__r.__ptr_), __cntrl_(__r.__cntrl) {
        __r.__ptr_ = 0;
        __r.__cntrl_ = 0;
}

template
shared_ptr<_Tp>::~shared_ptr(){
    if (__cntrl_)
        __cntrl_->__release_shared();
}

shared_ptr的实现虽然比unique_ptr复杂了一些,但是如果你能读懂unique_ptr的源代码,那shared_ptr的源代码对你来说也不算个事。因为篇幅的关系,对shared_ptr的分析就到这里。最后顺便说说一个关于效率的话题,我们已经看到了,shared_ptr内部维护了两个指针,如果你直接调用构造函数,就想这样:

class Widget;

auto sp = shared_ptr(new Widget());

这里实际分配了两次内存,第一次是调用new Widget()的时候,第二次则是在shared_ptr构造函数的内部构造__cntrl_的时候。分配内存是很昂贵的操作,所以标准库提供了make_shared()函数,让你一次分配全部所需的内存:

// file: memory

template
inline
typename enable_if::value, shared_ptr<_Tp>>::type
make_shared(_Args&& ...__args) {
    return shared_ptr<_Tp>::make_shared(std::forward<_Args>(__args)...);
}

template
template
shared_ptr<_Tp> shared_ptr<_Tp>::make_shared(_Args&& ...__args) {
    typedef __shared_ptr_emplace<_Tp, allocator<_Tp>>_CntrlBlk;
    typedef allocator<_CntrlBlk> _A2;
    typedef __alocator_destructor<_A2> _D2;

    _A2 __a2;
    unique_ptr<_CntrlBlk, _D2> __hold2(__a2.allocate(1), _D2(__a2, 1));
    ::new(__hold2.get()) _CntrlBlk(__a2, std::forward<_Args>(_args)...);
    shared_ptr<_Tp> __r;
    __r.__ptr_ = __hold2.get()->get();
    __r.__cntrl_ = __hold2.release();
    return __r;
}

我们可以看到,确实只分配了一次内存。注意内存的分配是在这里:

unique_ptr<_CntrlBlk, _D2> __hold2(__a2.allocate(1), _D2(__a2, 1));

而不是:

::new(__hold2.get()) _CntrlBlk(__a2, std::forward<_Args>(_args)...);

这里是调用placement new, 在__hold2的地址上构造一个__CntrBlk__CntrBlk的类型是__shared_ptr_emplace,它的定义如下:

// file: memory

template
class __shared_ptr_emplace : public __shared_weak_count {
    __compressed_pair<_Alloc, _Tp> __data_;

public:
    template
    __shared_ptr_emplace(_Alloc __a, _Args&& ...__args)
        : __data_(piecewise_construct, std::forward_as_typle(__a),
        std::forward_as_tuple(std::forward<_Args>(__args)...)){}

    // ...
};

可见make_shared()shared_ptr的成员打包到一个__shared_ptr_emplace中,一次性在堆中构造出一个__shared_ptr_emplace,然后再拆包分配给shared_ptr的成员变量。

你可能感兴趣的:(C++11 标准库源代码剖析:连载之四)