一、概念
我们要讨论八种智能指针,分别是:auto_ptr(VC版)、auto_ptr(VS版或Linux版)、scoped_ptr、scoped_array、shared_array、weak_ptr、instrusive_ptr,这篇文章主要详细分析auto_ptr(VS)。
智能指针是在堆上管理内存以及能够自动释放内存的工具,其本质是一个类。在类中分别重载了"=、*、->",目的是模拟指针,让其具有指针的能力。
值得注意的是,在auto_ptr(vs版)中,要着重理解指针指向的内存的拥有权问题(在下文中会详细讲解),其操作十分精妙,以此避免内存泄露的情况发生,但auto_ptr不提倡使用,有很多坑,但作为源码分析足够了。
二、源码剖析
下面是vs2019中auto_ptr智能指针的核心代码:(注释为函数的解释)
template<class _Ty>
class auto_ptr;
template<class _Ty>
struct auto_ptr_ref //支持将auto_ptr作为临时右值使用
{ // proxy reference for auto_ptr copying
explicit auto_ptr_ref(_Ty *_Right)
: _Ref(_Right)
{ // construct from generic pointer to auto_ptr ptr
}
_Ty *_Ref; // generic pointer to auto_ptr ptr
};
template<class _Ty>
class auto_ptr
{ // wrap an object pointer to ensure destruction
public:
typedef _Ty element_type;
explicit auto_ptr(_Ty * _Ptr = 0) _NOEXCEPT //构造函数
: _Myptr(_Ptr)
{ // construct from object pointer
}
auto_ptr(auto_ptr& _Right) _NOEXCEPT //拷贝构造函数,把原对象指针的指向关掉,并用新对象的指针指向原内存
: _Myptr(_Right.release())
{ // construct by assuming pointer from _Right auto_ptr
}
auto_ptr(auto_ptr_ref<_Ty> _Right) _NOEXCEPT//拷贝构造,用于接收临时的常类型对象
{ // construct by assuming pointer from _Right auto_ptr_ref
_Ty * _Ptr = _Right._Ref;
_Right._Ref = 0; // release old
_Myptr = _Ptr; // reset this
}
template<class _Other> //这是强制类型转换函数
operator auto_ptr<_Other>() _NOEXCEPT
{ // convert to compatible auto_ptr
return (auto_ptr<_Other>(*this));
}
template<class _Other>
operator auto_ptr_ref<_Other>() _NOEXCEPT //类型转换,使得类的继承关系得以支持
{ // convert to compatible auto_ptr_ref
_Other * _Cvtptr = _Myptr; // test implicit conversion
auto_ptr_ref<_Other> _Ans(_Cvtptr);
_Myptr = 0; // pass ownership to auto_ptr_ref
return (_Ans);
}
template<class _Other> //=重载,看_Other类型的重载函数
auto_ptr& operator=(auto_ptr<_Other>& _Right) _NOEXCEPT
{ // assign compatible _Right (assume pointer)
reset(_Right.release());
return (*this);
}
template<class _Other>
auto_ptr(auto_ptr<_Other>& _Right) _NOEXCEPT //拷贝构造
: _Myptr(_Right.release())
{ // construct by assuming pointer from _Right
}
auto_ptr& operator=(auto_ptr& _Right) _NOEXCEPT //=重载
{ // assign compatible _Right (assume pointer)
reset(_Right.release());
return (*this);
}
auto_ptr& operator=(auto_ptr_ref<_Ty> _Right) _NOEXCEP //=重载
{ // assign compatible _Right._Ref (assume pointer)
_Ty * _Ptr = _Right._Ref;
_Right._Ref = 0; // release old
reset(_Ptr); // set new
return (*this);
}
~auto_ptr() _NOEXCEPT
{ // destroy the object
delete _Myptr;
}
_NODISCARD _Ty& operator*() const _NOEXCEPT //*重载运算符,给指针解引用
{ // return designated value
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myptr == 0)
{
_DEBUG_ERROR("auto_ptr not dereferencable");
}
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
return (*get());
}
_NODISCARD _Ty * operator->() const _NOEXCEPT //->重载运算符,得到指针
{ // return pointer to class object
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myptr == 0)
{
_DEBUG_ERROR("auto_ptr not dereferencable");
}
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
return (get());
}
_NODISCARD _Ty * get() const _NOEXCEPT //get函数,得到指针
{ // return wrapped pointer
return (_Myptr);
}
_Ty * release() _NOEXCEPT //解除原指针的指向,用临时的指针指向空间,防止内存泄露
{ // return wrapped pointer and give up ownership
_Ty * _Tmp = _Myptr;
_Myptr = 0;
return (_Tmp);
}
void reset(_Ty * _Ptr = 0) //重新绑定指向的对象,而原来的对象则会被释放。就是我不想指向你了,我想指向其它地方
{ // destroy designated object and store new pointer
if (_Ptr != _Myptr)
delete _Myptr;
_Myptr = _Ptr;
}
private:
_Ty * _Myptr; // the wrapped object pointer
};
详细讲解:
①此类中写了一个构造函数,一个析构函数,三个拷贝构造函数,三个赋值重载函数,一个*重载函数,一个->重载函数,一个get函数,一个release函数,一个reset函数,两个类型转换的运算符重载函数(类型转换没有分析透,还请见谅,若有大神能够在评论区讲一讲,感激不尽),各函数的功能已在代码注释中有解释。
②精彩部分讲解:
(1)release函数:在要消除指针指向对象时,我们不能直接让指针赋值为空,因为这样的话,没有指针指向的内存将造成空间泄露,此时就可以声明一个临时指针指向空间,并用返回值返回该指针。
③RAII原则,auto_ptr保证的就是RAII对资源的所有权中的变性类型。
RAII概念:RAII,称为“资源获取就是初始化”,是C++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象。
RAII对资源的所有权的常性类型:指获取资源的地点是构造函数,释放点是析构函数,并且在这两点之间的一段时间里,任何对该RAII类型实例的操纵都不应该从它手里夺走资源的所有权。
RAII对资源的所有权的变性类型:指可以中途被设置为接管另一个资源,或者干脆被置为不拥有任何资源。外部初始化类型是指资源在外部被创建,并被传给RAII实例的构造函数,后者进而接管了其所有权。