C++ 中的四种智能指针

824 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

引言:为什么需要智能指针?

C++赋予了程序员直接操控底层内存的能力,同时,也对程序员的能力提出了要求,必须要对申请的内存空间进行释放,而且释放时机也必须准确,否则就会出现内存泄露,useafterfree等异常。寻找一种简单完美的方式解决这个问题一直是C++程序员需要关注的,在Android平台上使用了StrongPoint的方式,相当于将原有的指针包装成一个类,通过对指针reference次数的管理来实现对应空间的准确释放,在C++中则也使用了类似的方式,使用了智能指针类,通过引用计数来管理智能指针指向的内存空间,只有当指针引用计数为空时,才进行对象的销毁。

接下来,我们就来一起看看C++的四种智能指针。

C++的四种智能指针

代码位置(参考AOSP代码)

www.aospxref.com/android-12.… 基础接口

T* get();
T& operator*();
T* operator->();
T& operator=(const T& val);
T* release();
void reset (T* ptr = nullptr);
  • get()返回封装在内部的指针
  • operator*解引用原生指针
  • operator->相当于对原生指针做->操作
  • operator=指针拷贝操作
  • release将内部封装的指针置为nullptr,返回置空前的值
  • reset将内部封装的指针重置为ptr的值

1.auto_ptr(C++98 的方案,C11 已抛弃)采用所有权模式

auto_ptr<std::string> p1 (new string ("test"));
auto_ptr<std::string> p2;
p2 = p1; //赋值成功,此时p2拥有原生指针的所有权,后续再使用p1指针时就会报错

观察auto_ptr的赋值重载函数,会将等号右面的point reset

#if _LIBCPP_STD_VER <= 14 || defined(_LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR)
template <class _Tp>
struct _LIBCPP_DEPRECATED_IN_CXX11 auto_ptr_ref
{
    _Tp* __ptr_;
};
...
     _LIBCPP_INLINE_VISIBILITY auto_ptr& operator=(auto_ptr_ref<_Tp> __p) throw()
         {reset(__p.__ptr_); return *this;}

auto_ptr是独占的,当赋值之后,存在潜在的无法检测的内存异常。

2.unique_ptr

unique_ptr 实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象,主要为了避免资源泄露。上面的例子:

unique_ptr<std::string> p1 (new string ("test"));
unique_ptr<std::string> p2;
p2 = p1; //赋值失败,此时编译器会认为此操作非法,从而避免出现异常

同样,我们来看看它的赋值重载函数,只发现了移动构造和移动赋值函数,正常的拷贝构造和赋值拷贝是被禁用的

    unique_ptr& operator=(unique_ptr&& u) noexcept;
    template <class U, class E> unique_ptr& operator=(unique_ptr<U, E>&& u) noexcept;
    unique_ptr& operator=(nullptr_t) noexcept;

3.shared_ptr(共享型,强引用)

shared_ptr 实现共享式拥有概念,多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。过成员函数 use_count() 来查看资源的所有者个数,调用 release() 时,当前指针会释放资源所有权,计数减一,当计数等于 0 时,资源会被释放。 我们来看看其释放的过程:

__shared_weak_count* __cntrl_;//一个弱引用计数
...
template<class _Tp> 
shared_ptr<_Tp>::~shared_ptr()
{
    if (__cntrl_)
        __cntrl_->__release_shared();
}

    _LIBCPP_INLINE_VISIBILITY
    bool __release_shared() _NOEXCEPT {                                                                                                                                                                     
      if (__libcpp_atomic_refcount_decrement(__shared_owners_) == -1) {//减法操作后为-1,代表此时已经可以销毁了
        __on_zero_shared();
        return true;
      }
      return false;
    }

template <class _Tp, class _Dp, class _Alloc>
void
__shared_ptr_pointer<_Tp, _Dp, _Alloc>::__on_zero_shared() _NOEXCEPT                                                                                                                                        
{
    __data_.first().second()(__data_.first().first());
    __data_.first().second().~_Dp();//调用删除器析构,彻底释放资源
}

4.weak_ptr(弱引用)

weak_ptr 是一种不控制对象生命周期的智能指针,它指向一个 shared_ptr 管理的对象。进行该对象的内存管理的是那个强引用的 shared_ptr。weak_ptr 设计的目的是为配合shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作,它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造,,它的构造和析构不会引起引用记数的增加或减少。

weak_ptr 是用来解决 shared_ptr 相互引用时的死锁问题,如果说两个 shared_ptr 相互引用, 那么这两个指针的引用计数永远不可能下降为0,也就是资源永远不会释放。 当两个智能指针都是 shared_ptr 类型的时候,析构时两个资源引用计数会减一,但是两者引 用计数还是为 1,导致跳出函数时资源没有被释放(的析构函数没有被调用),解决办法:把 其中一个改为weak_ptr就可以。