C++的智能指针auto_ptrunqiue_ptr源码解析
Posted 彼 方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++的智能指针auto_ptrunqiue_ptr源码解析相关的知识,希望对你有一定的参考价值。
C++的智能指针auto_ptr、unqiue_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
那几个要简单很多,下面对其内容进行分析:
- 有一个类成员:
_M_ptr
(智能指针持有的资源) release
方法用的比较频繁,作用是将当前auto_ptr
的_M_ptr
通过返回值传出去,然后将自身的_M_ptr
给置为空,即调用release
后,当前的auto_ptr
立刻失效reset
方法是如果当前的_M_ptr
不为空的话,就将其释放,并将传入参数的值赋予它。为空的话不执行任何操作- 普通构造函数
explicit auto_ptr(element_type* __p = 0)
被explicit
修饰了,也就是说它拒绝隐式转换,所以不能这样进行初始化操作:char* p = new char(0); auto_ptr ptr = p;
- 两个拷贝构造函数都是使用了
release
方法实现的 - 赋值函数是使用
reset
方法实现的 - 析构函数负责释放
_M_ptr
的内存 - 和
shared_ptr
一样也重载了*
和->
运算符,故auto_ptr
也是具备和普通指针一样的行为
auto_ptr设计存在的一些缺陷:
- 不要使用
auto_ptr
对象保存指向静态分配对象的指针,否则,当auto_ptr
对象本身被撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为 - 不要使用两个
auto_ptr
对象指向同一对象,导致这个错误的一种明显方式是,使用同一指针来初始化或者reset
两个不同的auto_ptr
对象。另一种导致这个错误的微妙方式可能是,使用一个auto_ptr
对象的get
函数的结果来初始化或者reset
另一个auto_ptr
对象。 - 不要使用
auto_ptr
对象保存指向动态分配数组的指针。当auto_ptr
对象被删除的时候,它只释放一个对象,因为它使用的是普通delete
操作符,而不用数组的delete[]
操作符。 - 不要将
auto_ptr
对象存储在容器中。容器要求所保存的类型定义复制和赋值操作符,使它们表现得类似于内置类型的操作符,在复制(或者赋值)之后,两个对象必须具有相同值,auto_ptr
显然并不满足这个要求 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
的缺点大部分给解决了,下面分析代码具体内容:
unique_ptr
具有和普通指针一样的行为,这点是所有智能指针都具备的,就不赘述了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;
}
};
unique_ptr
删除了参数为const unique_ptr&
的拷贝构造函数和参数为const unique_ptr&
的赋值函数,auto_ptr
的第五个缺点得到解决unique_ptr
的拷贝构造函数和赋值函数需要的传入参数都是右值引用的类型(那个传入参数是nullptr_t
类型的除外),关于右值引用的问题可以看这篇文章《C++11的右值引用、移动语义(std::move)和完美转发(std::forward)详解》,这里就不展开讲了,不是本文重点。这里这样设计的目的就是要让使用者用一个即将失效的unique_ptr
来构造一个新的unique_ptr
,这样构建完之后老的unique_ptr
顺势消亡,新的unique_ptr
继续独占资源的所有权,符合正常的逻辑。- 因为
unique_ptr
依然是独占资源,所以和auto_ptr
一样,无法解决第四个缺点。第二个缺点也无法解决,这点和所有智能指针一样。 - 其余的实现就和
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_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 以上是关于C++的智能指针auto_ptrunqiue_ptr源码解析的主要内容,如果未能解决你的问题,请参考以下文章 |