C++的智能指针auto_ptr、unique_ptr源码解析

  • 1、前言
  • 2、源码准备
  • 3、源码解析
    • 3.1、auto_ptr解析
    • 3.2、unique_ptr解
    • 3.3、unique_ptr的一个偏特化版本
  • 4、智能指针相关内容的总结
  • 5、总结

1、前言

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

2、源码准备

本文是基于gcc-4.9.0的源代码进行分析,由于unique_ptr是C++11才加入标准的,所以低版本的gcc源码是没有unique_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、unique_ptr解

unique_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&>::typeoperator=(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>::typeoperator*() 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;
};

unique_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、unique_ptr的一个偏特化版本

template<typename _Tp, typename _Dp>
class unique_ptr<_Tp[], _Dp>
{...typename std::add_lvalue_reference<element_type>::typeoperator[](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 { }voidoperator()(_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>::typeoperator()(_Up*) const = delete;
};

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

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

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

auto_ptr unique_ptr shared_ptr weak_ptr
是否持有资源 Y Y Y Y
消亡是否影响资源释放 Y Y Y N
是否独占资源 Y Y N N
是否具有普通指针的行为 Y Y Y N
能否转换为shared_ptr
(有条件地转换也算)
Y Y Y Y
是否支持自定义释放内存的方法 N Y Y N
是否完全支持容器的各种行为 N N Y Y

5、总结

本文先是介绍了auto_ptr这个智能指针的设计,然后总结出它的缺点,C++标准委员会已经不建议使用auto_ptr了,所以大家平时开发中还是不要用这个了。而C++11为了解决auto_ptr存在的这些问题,设计了一个新的智能指针unique_ptrunique_ptr也确实对auto_ptr设计上不合理的地方进行了改正。至此C++四种智能指针就全部讲解完毕了。

最后,如果大家觉得本文写得好的话麻烦点赞收藏关注一下谢谢,也可以关注该专栏,以后会有更多优质文章输出的。

C++的智能指针auto_ptr、unique_ptr源码解析相关推荐

  1. 智能聊天机器人实现(源码+解析)

    前言: 之前写了一篇  <美女图片采集器 (源码+解析)> 得到了众多朋友的支持, 发现这样系列的教程还是挺受欢迎的, 也激励我继续写下去. 也在那一篇文章中提过, 美女图片采集只是我先前 ...

  2. 智能聊天机器人实现 源码+解析

    前言: 今天带来的是智能聊天机器人实现(源码+解析), 和上一篇教程一样, 当你没有女朋友的时候, 可以用它来打发时间.这里的API是图灵机器人提供的, 实现一个十分强大的机器人. 具体功能包括: • ...

  3. C++ 智能指针最佳实践源码分析

    作者:lucasfan,腾讯 IEG Global Pub.Tech. 客户端工程师 智能指针在 C++11 标准中被引入真正标准库(C++98 中引入的 auto_ptr 存在较多问题),但目前很多 ...

  4. C++ -- 智能指针 auto_ptr,unique_ptr,shared_ptr的简单实现和原理

    一,为什么需要智能指针 智能指针是一种预防型的内存泄漏的解决方案.由于C++没有垃圾回收器机制,所以每次new出来的资源都需要手动的delete,如果没有手动释放,就会造成资源泄漏等问题.因此,为了避 ...

  5. 智能指针(unique_ptr、shared_ptr、weak_ptr)

    主要参考链接:C++ 智能指针最佳实践&源码分析 参考链接:C++11 make_shared - 简书 智能指针在 C++11 标准中被引入真正的标准库(C++98 中引入的 auto_pt ...

  6. 智能聊天机器人实现(源码+析)

    前言: 今天带来的是智能聊天机器人实现(源码+解析), 和上一篇教程一样, 当你没有女朋友的时候, 可以用它来打发时间.这里的API是图灵机器人提供的, 实现一个十分强大的机器人. 具体功能包括: • ...

  7. 32. 对c++中的smart pointer四个智能指针shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解

    C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被11弃用. 智能指针的作用是管理一个指针, ...

  8. c++系列 —— 智能指针auto_ptr和unique_ptr

    往期地址: c++系列一 -- c++的封装 c++系列二 -- c++的继承 c++系列三 -- 继承和多态特性 c++系列四 -- 运算符重载 c++系列五 -- 静态成员和静态类 c++系列六 ...

  9. C++11新特性——智能指针之unique_ptr

    此课件及源代码来自B站up主:码农论坛,该文章仅作为本人学习笔记使用. unique_ptr独享它指向的对象,也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁 ...

最新文章

  1. 比特币现金与比特币呈竞争关系 分析表示加密货币之间的竞争不是坏事
  2. L2-1 包装机 (25 分)(STL43行代码)
  3. android 图片轮播
  4. 九度OJ 1035:找出直系亲属(二叉树)
  5. 2018 开发者生态报告:Java 最流行,Go 最有潜力,JavaScript最常用
  6. 物质的粒子应该是空心的
  7. 自学前端的日子,记录我的秃头之旅
  8. 分享大三改进后的python写的【银行管理系统】,超详细 【内附源码】
  9. 三相 AC-DC 变换电路(B 题)-- 2021 年全国大学生电子设计竞赛
  10. MIPI-DSI 三种 Video Mode 理解
  11. Windows xp定时关机命令
  12. 计算机学院谭钊琦,中山大学南方学院-电气与计算机工程学院
  13. 极光短信在程序中(JAVA)的使用
  14. c语言个人所得税的打印思路,C语言编写一个计算个人所得税的程序,要求输入收入金额,能够输...
  15. 【日语学习】日语 N2 词汇核心动词 200 个
  16. python中的while语句
  17. 易得无价宝,难得有情郎
  18. 专升本计算机的数学考不考正态分布,高考成绩不一定是正态分布
  19. 双通道连续波多普勒雷达测速模型 - Matlab仿真
  20. JAVA与西门子S7 PLC通信,方式一:S7connector

热门文章

  1. C# 递归的应用 TreeView递归绑定数据
  2. Android实现实时视频聊天功能|源码 Demo 分享
  3. 1恢复 群晖raid_【官方】群晖官方数据恢复方法
  4. RADIUS协议 [收藏]
  5. 【黑金ZYNQ7000系列原创视频教程】01.熟悉vivadomdash;mdash;纯逻辑led实验
  6. 进程管理(二十二)—CFS调度器
  7. html中调用js带参数传递,JS传参技巧总结
  8. 铁夫破词之英文名字的由来(总结)
  9. 凹凸世界搬运工机器人图片_【图片】【原创】凹凸世界后续剧情_凹凸世界吧_百度贴吧...
  10. Tableau数据分析笔记-Chapter13雷达图和凹凸图