C++的智能指针auto_ptrunqiue_ptr源码解析

Posted 彼 方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++的智能指针auto_ptrunqiue_ptr源码解析相关的知识,希望对你有一定的参考价值。

1、前言

本文仅对C++智能指针auto_ptr、unqiue_ptr源码进行解析,需要读者有一定的C++基础并且对智能指针有所了解,本文并不对智能指针的使用方法、使用场景、效率等方面进行阐述分析,这些知识需自行查阅相关书籍去了解。
建议大家先看一下这篇文章《C++11的智能指针shared_ptr、weak_ptr源码解析》,里面详细介绍了shared_ptr、weak_ptr相关的内容,这些内容对阅读本文没有什么帮助,但是最后我们要拿这几个智能指针进行对比,所以需要我们先了解相关的知识。
auto_ptr和unqiue_ptr与之前讲的shared_ptr有着显著差异,shared_ptr是共享型的智能指针,而auto_ptr或unqiue_ptr是独占型的,某一时刻只能由一个auto_ptr或unqiue_ptr持有同一个资源。

2、源码准备

本文是基于gcc-4.9.0的源代码进行分析,由于unqiue_ptr是C++11才加入标准的,所以低版本的gcc源码是没有unqiue_ptr的,建议选择4.9.0或更新的版本去学习,不同版本的gcc源码差异应该不小,但是原理和设计思想的一样的,下面给出源码下载地址
http://ftp.gnu.org/gnu/gcc

3、源码解析

3.1、auto_ptr解析

auto_ptr位于libstdc++-v3\\include\\backward\\auto_ptr.h

template<typename _Tp>
class auto_ptr
{
private:
    _Tp* _M_ptr;
  
public:
    typedef _Tp element_type;

    explicit auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }

    auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }

    template<typename _Tp1>
    auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { }

    auto_ptr& operator=(auto_ptr& __a) throw()
    {
        reset(__a.release());
        return *this;
    }

    template<typename _Tp1>
    auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw()
    {
        reset(__a.release());
        return *this;
    }

    ~auto_ptr() { delete _M_ptr; }

    element_type& operator*() const throw() 
    {
        _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
        return *_M_ptr; 
    }

    element_type* operator->() const throw() 
    {
        _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
        return _M_ptr; 
    }

    element_type* get() const throw() { return _M_ptr; }

    element_type* release() throw()
    {
        element_type* __tmp = _M_ptr;
        _M_ptr = 0;
        return __tmp;
    }

    void reset(element_type* __p = 0) throw()
    {
        if (__p != _M_ptr)
        {
            delete _M_ptr;
            _M_ptr = __p;
        }
    }

    auto_ptr(auto_ptr_ref<element_type> __ref) throw()
        :_M_ptr(__ref._M_ptr)
    {
    }

    auto_ptr& operator=(auto_ptr_ref<element_type> __ref) throw()
    {
        if (__ref._M_ptr != this->get())
        {
            delete _M_ptr;
            _M_ptr = __ref._M_ptr;
        }
        return *this;
    }

    template<typename _Tp1>
    operator auto_ptr_ref<_Tp1>() throw()
    { return auto_ptr_ref<_Tp1>(this->release()); }

    template<typename _Tp1>
    operator auto_ptr<_Tp1>() throw()
    { return auto_ptr<_Tp1>(this->release()); }
} _GLIBCXX_DEPRECATED;

从代码中可以看出auto_ptr确实比之前讲过的shared_ptr那几个要简单很多,下面对其内容进行分析:

  1. 有一个类成员:_M_ptr(智能指针持有的资源)
  2. release方法用的比较频繁,作用是将当前auto_ptr_M_ptr通过返回值传出去,然后将自身的_M_ptr给置为空,即调用release后,当前的auto_ptr立刻失效
  3. reset方法是如果当前的_M_ptr不为空的话,就将其释放,并将传入参数的值赋予它。为空的话不执行任何操作
  4. 普通构造函数explicit auto_ptr(element_type* __p = 0)explicit修饰了,也就是说它拒绝隐式转换,所以不能这样进行初始化操作:char* p = new char(0); auto_ptr ptr = p;
  5. 两个拷贝构造函数都是使用了release方法实现的
  6. 赋值函数是使用reset方法实现的
  7. 析构函数负责释放_M_ptr的内存
  8. shared_ptr一样也重载了*->运算符,故auto_ptr也是具备和普通指针一样的行为

auto_ptr设计存在的一些缺陷:

  1. 不要使用auto_ptr对象保存指向静态分配对象的指针,否则,当auto_ptr对象本身被撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为
  2. 不要使用两个auto_ptr对象指向同一对象,导致这个错误的一种明显方式是,使用同一指针来初始化或者reset两个不同的auto_ptr对象。另一种导致这个错误的微妙方式可能是,使用一个auto_ptr对象的get函数的结果来初始化或者reset另一个auto_ptr对象。
  3. 不要使用auto_ptr对象保存指向动态分配数组的指针。当auto_ptr对象被删除的时候,它只释放一个对象,因为它使用的是普通delete操作符,而不用数组的delete[]操作符。
  4. 不要将auto_ptr对象存储在容器中。容器要求所保存的类型定义复制和赋值操作符,使它们表现得类似于内置类型的操作符,在复制(或者赋值)之后,两个对象必须具有相同值,auto_ptr显然并不满足这个要求
  5. auto_ptr在被用于拷贝构造函数或赋值函数之后,它的值就会失效了,这点不太符合我们正常的逻辑思维。

上面介绍的缺陷除了第二点以外的在shared_ptr中都被解决了(第二点是所有智能指针的通病,这其实不太能叫做缺点,更像是使用者的误用),第四点、第五点显然shared_ptr是直接解决了的,而第一点和第三点涉及到了资源释放的问题,shared_ptr支持使用者自定义内存释放器,那么当shared_ptr接管的是一个静态分配对象的指针时,在内存释放器里啥都不干就行了,而接管的如果是指向动态分配数组的指针时,内存释放器里面使用delete[]去释放内存就行了。

3.2、unqiue_ptr解

unqiue_ptr位于libstdc++-v3\\include\\bits\\unique_ptr.h

template <typename _Tp, typename _Dp = default_delete<_Tp> >
class unique_ptr
{
    class _Pointer
    {
        template<typename _Up>
        static typename _Up::pointer __test(typename _Up::pointer*);

        template<typename _Up>
        static _Tp* __test(...);

        typedef typename remove_reference<_Dp>::type _Del;

    public:
        typedef decltype(__test<_Del>(0)) type;
    };

    typedef std::tuple<typename _Pointer::type, _Dp> __tuple_type;
    __tuple_type _M_t;

public:
    typedef typename _Pointer::type   pointer;
    typedef _Tp                       element_type;
    typedef _Dp                       deleter_type;

    constexpr unique_ptr() noexcept
        :_M_t()
    { static_assert(!is_pointer<deleter_type>::value, "constructed with null function pointer deleter"); }

    explicit unique_ptr(pointer __p) noexcept
        :_M_t(__p, deleter_type())
    { static_assert(!is_pointer<deleter_type>::value, "constructed with null function pointer deleter"); }

    unique_ptr(pointer __p, typename conditional<is_reference<deleter_type>::value, deleter_type, const deleter_type&>::type __d) noexcept
        :_M_t(__p, __d)
    {
    }

    unique_ptr(pointer __p, typename remove_reference<deleter_type>::type&& __d) noexcept
        :_M_t(std::move(__p), std::move(__d))
    { static_assert(!std::is_reference<deleter_type>::value, "rvalue deleter bound to reference"); }


    constexpr unique_ptr(nullptr_t) noexcept : unique_ptr() { }

    unique_ptr(unique_ptr&& __u) noexcept
        :_M_t(__u.release(), std::forward<deleter_type>(__u.get_deleter()))
    {
    }

    template<typename _Up, typename _Ep, typename = _Require<is_convertible<typename unique_ptr<_Up, _Ep>::pointer, pointer>, __not_<is_array<_Up>>, typename conditional<is_reference<_Dp>::value, is_same<_Ep, _Dp>, is_convertible<_Ep, _Dp>>::type>>
    unique_ptr(unique_ptr<_Up, _Ep>&& __u) noexcept
        :_M_t(__u.release(), std::forward<_Ep>(__u.get_deleter()))
    {
    }

    ~unique_ptr() noexcept
    {
        auto& __ptr = std::get<0>(_M_t);
        if (__ptr != nullptr)
            get_deleter()(__ptr);
        __ptr = pointer();
    }

    unique_ptr& operator=(unique_ptr&& __u) noexcept
    {
        reset(__u.release());
        get_deleter() = std::forward<deleter_type>(__u.get_deleter());
        return *this;
    }

    template<typename _Up, typename _Ep>
    typename enable_if< __and_<is_convertible<typename unique_ptr<_Up, _Ep>::pointer, pointer>, __not_<is_array<_Up>>>::value, unique_ptr&>::type
    operator=(unique_ptr<_Up, _Ep>&& __u) noexcept
    {
        reset(__u.release());
        get_deleter() = std::forward<_Ep>(__u.get_deleter());
        return *this;
    }

    unique_ptr& operator=(nullptr_t) noexcept
    {
        reset();
        return *this;
    }

    typename add_lvalue_reference<element_type>::type
    operator*() const
    {
        _GLIBCXX_DEBUG_ASSERT(get() != pointer());
        return *get();
    }

    pointer operator->() const noexcept
    {
        _GLIBCXX_DEBUG_ASSERT(get() != pointer());
        return get();
    }

    pointer get() const noexcept
    { return std::get<0>(_M_t); }

    deleter_type& get_deleter() noexcept
    { return std::get<1>(_M_t); }

    const deleter_type& get_deleter() const noexcept
    { return std::get<1>(_M_t); }

    explicit operator bool() const noexcept
    { return get() == pointer() ? false : true; }

    pointer release() noexcept
    {
        pointer __p = get();
        std::get<0>(_M_t) = pointer();
        return __p;
    }

    void reset(pointer __p = pointer()) noexcept
    {
        using std::swap;
        swap(std::get<0>(_M_t), __p);
        if (__p != pointer())
            get_deleter()(__p);
    }

    void swap(unique_ptr& __u) noexcept
    {
        using std::swap;
        swap(_M_t, __u._M_t);
    }

    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
};

unqiue_ptr是C++11版本之后官方推荐用来替代auto_ptr的一个智能指针,它将前面讲述的auto_ptr的缺点大部分给解决了,下面分析代码具体内容:

  1. unique_ptr具有和普通指针一样的行为,这点是所有智能指针都具备的,就不赘述了
  2. unique_ptr有一个类成员_M_t,类型是tuple(元组),元组的第一个成员是unique_ptr接管的指针,第二个成员是内存释放器,官方也给出了默认的删除器,所以一般情况下我们不用自己去指定(这点和shared_ptr差不多)。因为可以指定内存释放器了,所以auto_ptr的第一和第三个缺点就可以直接解决掉了。官方给的默认内存释放器如下,这是一个典型的可调用对象类,内容比较简单就不作分析了,里面重载了()运算符,void operator()(_Tp* __ptr)里面释放了传入参数__ptr的内存
template<typename _Tp>
struct default_delete
{
    constexpr default_delete() noexcept = default;

    template<typename _Up, typename = typename enable_if<is_convertible<_Up*, _Tp*>::value>::type> default_delete(const default_delete<_Up>&) noexcept { }

    void operator()(_Tp* __ptr) const
    {
        static_assert(!is_void<_Tp>::value, "can't delete pointer to incomplete type");
        static_assert(sizeof(_Tp)>0, "can't delete pointer to incomplete type");
        delete __ptr;
    }
};
  1. unique_ptr删除了参数为const unique_ptr&的拷贝构造函数和参数为const unique_ptr&的赋值函数,auto_ptr的第五个缺点得到解决
  2. unique_ptr的拷贝构造函数和赋值函数需要的传入参数都是右值引用的类型(那个传入参数是nullptr_t类型的除外),关于右值引用的问题可以看这篇文章《C++11的右值引用、移动语义(std::move)和完美转发(std::forward)详解》,这里就不展开讲了,不是本文重点。这里这样设计的目的就是要让使用者用一个即将失效的unique_ptr来构造一个新的unique_ptr,这样构建完之后老的unique_ptr顺势消亡,新的unique_ptr继续独占资源的所有权,符合正常的逻辑。
  3. 因为unique_ptr依然是独占资源,所以和auto_ptr一样,无法解决第四个缺点。第二个缺点也无法解决,这点和所有智能指针一样。
  4. 其余的实现就和auto_ptr大同小异了,毕竟都是独占型的智能指针,这里就不多作介绍了

3.3、unqiue_ptr的一个偏特化版本

template<typename _Tp, typename _Dp>
class unique_ptr<_Tp[], _Dp>
{
...
    typename std::add_lvalue_reference<element_type>::type
    operator[](size_t __i) const
    {
        _GLIBCXX_DEBUG_ASSERT(get() != pointer());
        return get()[__i];
    }
...
}

template<typename _Tp>
struct default_delete<_Tp[]>
{
private:
    template<typename _Up> using __remove_cv = typename remove_cv<_Up>::type;
    template<typename _Up> using __is_derived_Tp = __and_< is_base_of<_Tp, _Up>, __not_<is_same<__remove_cv<_Tp>, __remove_cv<_Up>>> >;

public:
    constexpr default_delete() noexcept = default;

    template<typename _Up, typename = typename enable_if<!__is_derived_Tp<_Up>::value>::type>
    default_delete(const default_delete<_Up[]>&) noexcept { }

    void
    operator()(_Tp* __ptr) const
    {
        static_assert(sizeof(_Tp)>0, "can't delete pointer to incomplete type");
        delete [] __ptr;
    }

    template<typename _Up>
    typename enable_if<__is_derived_Tp<_Up>::value>::type
    operator()(_Up*) const = delete;
};

官方还给出了一个unqiue_ptr的偏特化版本,上面的代码中有所省略,因为大部分内容和4.2小节讲的那些是一样的。由于这个版本的unqiue_ptr保存的是指向动态分配数组的指针,所以重载了一个[]运算符,资源释放也需要用delete[]的形式了,官方同样给出了一个可用的内存释放器,但是这次这个没有作为默认选项,应该是官方希望我们自己重视内存的释放问题吧。

4、智能指针相关内容的总结

下面通过表格的形式呈现给大家看,方便大家记忆(Y表示支持,N表示不支持)

auto_ptrunique_ptrshared_ptrweak_ptr
是否持有资源YYYY
消亡是否影响资源释放YYYN
是否独占资源YYNN
是否具有普通指针的行为YYYN
能否转换为shared_ptr
(有条件地转换也算)
YYYY
是否支持自定义释放内存的方法NYYN
是否完全支持容器的各种行为NNYY以上是关于C++的智能指针auto_ptrunqiue_ptr源码解析的主要内容,如果未能解决你的问题,请参考以下文章

C++智能指针

C++ 智能指针性能

C++智能指针类模板

C++ 和智能指针——智能指针在这种情况下有啥帮助?

C++ 中的智能指针-基础

C++智能指针简单剖析

(c)2006-2019 SYSTEM All Rights Reserved IT常识