跳转至

智能指针

智能指针定义在头文件 <memory> 中。

1 shared_ptr

  • 实现原理分析:
  • shared_ptr 内部保存一个对象指针和一个引用计数类 _Ref_count 指针,通过 _Ref_count 继承自 _Ref_count_base 中引用计数变量来判断是否需要对保存的指针进行 delete,当引用 计数变量==0 时,_Ref_count 会对保存的对象,以及 _Ref_count 自身资源进行释放
  • 引用计数类主要 2 个功能:管理内存、引用计数
  • 所有对 shared_ptr 对象的拷贝赋值,都会触发引用计数 +1。
  • shares_Ptr 对象的析构会触发引用计数 -1。

1.1 类 shared_ptr 分析

shared_ptr 公有继承自 _Ptr_base,在 shared_ptr 类中主要实现构造,赋值等操作,实际引用计数,对象指针数据都在 _Ptr_base 中实现 - shared_ptr 部分源码

class shared_ptr : public _Ptr_base<_Ty> { // class for reference counted resource management
private:
    using _Mybase = _Ptr_base<_Ty>;

public:
    using typename _Mybase::element_type;
//其中一个构造函数    
    template <class _Ux,
    enable_if_t<conjunction_v<conditional_t<is_array_v<_Ty>, _Can_array_delete<_Ux>, _Can_scalar_delete<_Ux>>,
    _SP_convertible<_Ux, _Ty>>,
    int> = 0>
    explicit shared_ptr(_Ux* _Px) { // construct shared_ptr object that owns _Px
#if _HAS_IF_CONSTEXPR
        if constexpr (is_array_v<_Ty>) {
            _Setpd(_Px, default_delete<_Ux[]>{});
        } else {
          //_Owner临时保存_Px指针
            _Temporary_owner<_Ux> _Owner(_Px);
            //_Set_ptr_rep_and_enable_shared函数用来给_Ptr_base中的_Ptr和_Rep赋值,Ref_count中也保存_Px指针
            _Set_ptr_rep_and_enable_shared(_Owner._Ptr, new _Ref_count<_Ux>(_Owner._Ptr));
            _Owner._Ptr = nullptr;
        }
    //声明使用_Ptr_base中保护函数get,并将它转出公有函数get
    using _Mybase::get;
    //析构会调用引用计数-1,当引用计数==0时,会释放资源
    ~shared_ptr() noexcept { // release resource
      this->_Decref();
    }
}

1.2 类 _Ptr_base 分析

_Ptr_base 中有 2 个重要的指针成员变量,一个是保存的数据对象的指针,另一个控制类型指针 _Ref_count_base,用来实现引用计数及删除保存对象指针所占内存功能 - _Ptr_base 部分源码

template <class _Ty>
class _Ptr_base { // base class for shared_ptr and weak_ptr
public:
//如果是数组型,将会移除[]
    using element_type = remove_extent_t<_Ty>;
//获取引用计数数
    _NODISCARD long use_count() const noexcept {
        return _Rep ? _Rep->_Use_count() : 0;
    }

protected:
//返回保存的对象的指针
    _NODISCARD element_type* get() const noexcept {
        return _Ptr;
    }

    constexpr _Ptr_base() noexcept = default;

    ~_Ptr_base() = default;
//移动构造会用到的函数,只交换数据,引用计数并不会增加或减少,_Other不再使用
    template <class _Ty2>
    void _Move_construct_from(_Ptr_base<_Ty2>&& _Right) noexcept {
        // implement shared_ptr's (converting) move ctor and weak_ptr's move ctor
        _Ptr = _Right._Ptr;
        _Rep = _Right._Rep;

        _Right._Ptr = nullptr;
        _Right._Rep = nullptr;
    }
//拷贝构造会使用的函数,引用计数+1
    template <class _Ty2>
    void _Copy_construct_from(const shared_ptr<_Ty2>& _Other) noexcept {
        // implement shared_ptr's (converting) copy ctor
        _Other._Incref();

        _Ptr = _Other._Ptr;
        _Rep = _Other._Rep;
    }
//使用新对象指针替换原指针,引用计数+1
    template <class _Ty2>
    void _Alias_construct_from(const shared_ptr<_Ty2>& _Other, element_type* _Px) noexcept {
        // implement shared_ptr's aliasing ctor
        _Other._Incref();

        _Ptr = _Px;
        _Rep = _Other._Rep;
    }
//使用新对象指针替换原指针,移动构造引用计数不变,_Other不再使用
    template <class _Ty2>
    void _Alias_move_construct_from(shared_ptr<_Ty2>&& _Other, element_type* _Px) noexcept {
        // implement shared_ptr's aliasing move ctor
        _Ptr = _Px;
        _Rep = _Other._Rep;

        _Other._Ptr = nullptr;
        _Other._Rep = nullptr;
    }

    template <class _Ty0>
    friend class weak_ptr; // specifically, weak_ptr::lock()

    template <class _Ty2>
    bool _Construct_from_weak(const weak_ptr<_Ty2>& _Other) noexcept {
        // implement shared_ptr's ctor from weak_ptr, and weak_ptr::lock()
        if (_Other._Rep && _Other._Rep->_Incref_nz()) {
            _Ptr = _Other._Ptr;
            _Rep = _Other._Rep;
            return true;
        }

        return false;
    }
//引用计数+1
    void _Incref() const noexcept {
        if (_Rep) {
            _Rep->_Incref();
        }
    }
//引用计数-1
    void _Decref() noexcept { // decrement reference count
        if (_Rep) {
            _Rep->_Decref();
        }
    }

private:
//保存对象的指针
    element_type* _Ptr{nullptr};
//用来控制引用计数及删除对象功能
    _Ref_count_base* _Rep{nullptr};



#if _HAS_STATIC_RTTI
//用来获取删除器函数指针
    template <class _Dx, class _Ty0>
    friend _Dx* get_deleter(const shared_ptr<_Ty0>& _Sx) noexcept;
#endif // _HAS_STATIC_RTTI
};

1.3 类 _Ref_count_base 分析

引用计数类 _Ref_count_base 是一个抽象类,定义了一些基础方法,主要处理引用计数 +-,以及何时 delete 保存的指针。它有一个子类 _Ref_count,在智能指针中使用的是这个子类 _Ref_count - _Ref_count_base 部分源码

// CLASS _Ref_count_base
class __declspec(novtable) _Ref_count_base { // common code for reference counting
private:
#ifdef _M_CEE_PURE
    // permanent workaround to avoid mentioning _purecall in msvcurt.lib, ptrustu.lib, or other support libs
    virtual void _Destroy() noexcept {
        _STD terminate();
    }

    virtual void _Delete_this() noexcept {
        _STD terminate();
    }
#else // ^^^ _M_CEE_PURE / !_M_CEE_PURE vvv
    virtual void _Destroy() noexcept     = 0; // destroy managed resource
    virtual void _Delete_this() noexcept = 0; // destroy self
#endif // _M_CEE_PURE
// 初始化引用计数值为1,这个值控制引用计数
    _Atomic_counter_t _Uses  = 1;
    _Atomic_counter_t _Weaks = 1;

public:
// 引用计数+1,_MT_INCR(_Uses)是微软实现的具有原子操作的+1
    void _Incref() noexcept { // increment use count
        _MT_INCR(_Uses);
    }

    void _Incwref() noexcept { // increment weak reference count
        _MT_INCR(_Weaks);
    }
// 引用计数-1 当引用计数==0时会执行一系列delete操作,删除保存的指针对象以及自身
    void _Decref() noexcept { // decrement use count
        if (_MT_DECR(_Uses) == 0) {
            _Destroy();
            _Decwref();
        }
    }

    void _Decwref() noexcept { // decrement weak reference count
        if (_MT_DECR(_Weaks) == 0) {
            _Delete_this();
        }
    }
//返回引用计数值
    long _Use_count() const noexcept {
        return static_cast<long>(_Uses);
    }

};

1.4 类 _Ref_count 分析

_Ref_count_Ref_count_base 的子类,它的作用是实现资源释放的具体操作。 - _Ref_count 部分源码

// CLASS TEMPLATE _Ref_count
template <class _Ty>
class _Ref_count : public _Ref_count_base { // handle reference counting for pointer without deleter
public:
//用待保存的对象构造实例对象
    explicit _Ref_count(_Ty* _Px) : _Ref_count_base(), _Ptr(_Px) {}

private:
//删除保存的对象
    virtual void _Destroy() noexcept override { // destroy managed resource
        delete _Ptr;
    }
//删除自身
    virtual void _Delete_this() noexcept override { // destroy self
        delete this;
    }
//智能指针保存的对象指针,与_Ptr_base中的对象指针是同一个
    _Ty* _Ptr;
};

1.5 示例

1.5.1 shared_ptr 几种赋值方式

C++ 中 std::shared_ptr 提供了多种赋值方式,用来改变智能指针所指向的对象或者共享对象所有权。以下是几种常见的 shared_ptr 赋值操作:

  1. 普通赋值(copy assignment)

    std::shared_ptr<T> ptr1(new T);
    std::shared_ptr<T> ptr2;
    ptr2 = ptr1; // 此时ptr1和ptr2共享同一个对象的所有权,引用计数加1
    

  2. 移动赋值(move assignment)(C++11 开始支持):

    std::shared_ptr<T> ptr1(new T);
    std::shared_ptr<T> ptr2;
    ptr2 = std::move(ptr1); // 移动所有权,ptr1不再拥有资源,ptr2接管,ptr1变为空指针
    

  3. 拷贝构造函数赋值

    std::shared_ptr<T> ptr1(new T);
    std::shared_ptr<T> ptr2(ptr1); // 通过拷贝构造函数创建一个新的shared_ptr,ptr1和ptr2共享同一个对象
    

  4. 模板赋值运算符

    std::shared_ptr<Derived> derivedPtr(new Derived);
    std::shared_ptr<Base> basePtr = derivedPtr; // 如果Derived是Base的派生类,则可以将derivedPtr赋给basePtr
    

  5. reset () 函数间接赋值

    std::shared_ptr<T> ptr;
    ptr.reset(new T); // 使用reset()分配一个新对象,如果ptr之前已有对象,则会释放旧对象并重新分配
    

  6. swap () 函数交换两个 shared_ptr

    std::shared_ptr<T> ptr1(new T);
    std::shared_ptr<T> ptr2(new T);
    using std::swap;
    swap(ptr1, ptr2); // 交换两个 shared_ptr 指向的对象,不会增加引用计数
    

每种赋值操作都会维护内部的引用计数器,并在适当的时候自动管理内存,确保当所有指向同一对象的 shared_ptr 都失效时,该对象会被正确地销毁。同时,在多线程环境中,引用计数的操作通常是原子性的以保证线程安全。

1.5.2 使用 make_shared 和使用 shared_ptr 构造初始化有什么不同

std::make_shared<T>() 和直接使用 std::shared_ptr<T> 构造函数初始化智能指针在内存管理、性能和安全性上存在显著的不同:

  1. 内存分配效率
  2. make_shared: 当使用 std::make_shared<T>(args...) 时,它会一次性为对象及其控制块(存储引用计数和其他元数据)分配连续的内存空间。这意味着只需要一次内存分配操作,从而提高了效率,减少了潜在的碎片,并且由于共享同一内存区域,访问控制块的速度也会更快。
  3. shared_ptr 构造函数: 直接通过 new T(args...) 创建对象并传给 std::shared_ptr<T>(new T(args...)) 构造函数时,会分别对对象和控制块进行两次独立的内存分配。这通常会导致额外的开销。

  4. 异常安全性

  5. make_shared: 如果 T 的构造过程中抛出了异常,那么通过 make_shared 分配的内存会被正确地释放,不会导致资源泄露。
  6. shared_ptr 构造函数: 当你先创建裸指针再将其传递给 shared_ptr 构造函数时,在两个步骤之间若发生异常,则可能导致对象已经构造但对应的 shared_ptr 没有成功创建,进而引发资源泄露问题。

  7. 内存布局优化

  8. make_shared: 内存布局更紧凑,有可能提升缓存局部性,从而提高程序性能。
  9. shared_ptr 构造函数: 对象和控制块在内存中可能不相邻,对性能可能稍有影响。

  10. 代码简洁性与可读性

  11. make_shared: 代码更加简洁,不需要显式删除原始指针。
  12. shared_ptr 构造函数: 需要显示地进行 new 操作以及处理可能出现的异常安全问题。

总结来说,std::make_shared<T>() 是推荐的实践方式,因为它提供了更好的性能、更高的内存利用率和更强的异常安全性。除非特定场景下无法使用 make_shared 或者需要更多灵活性,一般情况下都应优先考虑使用 make_shared

2 weak_ptr

  • 特点:
  • 用于解决 std::shared_ptr 的循环引用问题。
  • 它只能接受 weak_ptrshared_ptr,使用时需要先转成 shared_ptr 再通过 shared_ptr 去访问保存的指针对象
  • 它是一种弱引用,没有引用计数,不负责管理内存,它的存在与否不影响原指针对象的释放
  • 示例
    #include <iostream>
    #include <memory>
    
    int main() {
        // 创建一个 std::shared_ptr
        std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
    
        // 从 std::shared_ptr 创建一个 std::weak_ptr
        std::weak_ptr<int> weakPtr = sharedPtr;
    
        // 检查 weak_ptr 是否仍然有效
        if (std::shared_ptr<int> lockedPtr = weakPtr.lock()) {
            std::cout << "Value: " << *lockedPtr << std::endl;
        } else {
            std::cout << "Object has been deleted." << std::endl;
        }
    
        // 释放 sharedPtr,weakPtr 将失效
        sharedPtr.reset();
    
        // 再次检查 weak_ptr 是否仍然有效
        if (std::shared_ptr<int> lockedPtr = weakPtr.lock()) {
            std::cout << "Value: " << *lockedPtr << std::endl;
        } else {
            std::cout << "Object has been deleted." << std::endl;
        }
    
        return 0;
    }
    

2.1 类 weak_ptr 分析

weak_ptr 中有 3 个重要的函数,用来管理 weak_ptr,其中 lock 函数用来获取 shared_ptr - weak_ptr 部分源码及分析

// CLASS TEMPLATE weak_ptr
template <class _Ty>
class weak_ptr : public _Ptr_base<_Ty> { // class for pointer to reference counted resource
public:
//这个会释放引用的shared_ptr
    void reset() noexcept { // release resource, convert to null weak_ptr object
        weak_ptr{}.swap(*this);
    }

    void swap(weak_ptr& _Other) noexcept {
        this->_Swap(_Other);
    }
//用来判断被管理对象是否被删除
    _NODISCARD bool expired() const noexcept {
        return this->use_count() == 0;
    }
//用来获取引用的shared_ptr
    _NODISCARD shared_ptr<_Ty> lock() const noexcept { // convert to shared_ptr
        shared_ptr<_Ty> _Ret;
        //通过weak_ptr获得shared_ptr,如果成功则返回引用的shared_ptr,否则得到的是空shared_ptr
        (void) _Ret._Construct_from_weak(*this);
        return _Ret;
    }
};

2.2 类 _Ptr_base 分析

_Ptr_base_Construct_from_weak_Construct_from_weak 这 2 个函数很重要,实现了 shared_ptrweak_ptr 的转换 - _Ptr_base 部分源码

template <class _Ty>
class _Ptr_base { // base class for shared_ptr and weak_ptr
public:
    using element_type = remove_extent_t<_Ty>;

protected:
    _NODISCARD element_type* get() const noexcept {
        return _Ptr;
    }

    constexpr _Ptr_base() noexcept = default;

    ~_Ptr_base() = default;

    template <class _Ty0>
    friend class weak_ptr; // specifically, weak_ptr::lock()

    template <class _Ty2>
    bool _Construct_from_weak(const weak_ptr<_Ty2>& _Other) noexcept {
        // implement shared_ptr's ctor from weak_ptr, and weak_ptr::lock()
        if (_Other._Rep && _Other._Rep->_Incref_nz()) {
            _Ptr = _Other._Ptr;
            _Rep = _Other._Rep;
            return true;
        }

        return false;
    }
//shared_ptr转weak_ptr,如果引用计数控制指针_Rep不为空,则表示shared_ptr保存着有效指针对象,指针对象和引用计数指针赋值,弱引用计数+1
    template <class _Ty2>
    void _Weakly_construct_from(const _Ptr_base<_Ty2>& _Other) noexcept { // implement weak_ptr's ctors
        if (_Other._Rep) {
            _Ptr = _Other._Ptr;
            _Rep = _Other._Rep;
            _Rep->_Incwref();
        } else {
            _STL_INTERNAL_CHECK(!_Ptr && !_Rep);
        }
    }
//弱引用计数+1
    void _Incwref() const noexcept {
        if (_Rep) {
            _Rep->_Incwref();
        }
    }
//弱引用计数-1
    void _Decwref() noexcept { // decrement weak reference count
        if (_Rep) {
            _Rep->_Decwref();
        }
    }

private:
    element_type* _Ptr{nullptr};
    _Ref_count_base* _Rep{nullptr};

};

3 unique_ptr

  • unique_ptr 独占指针,与 shared_ptr 共享特性相反。
  • uniqe_ptr 不能拷贝构造拷贝赋值,但支持移动构造和移动赋值。
title: uniqe_ptr怎么实现不能构造的?
`uniqe_ptr`类中将拷贝构造和移动赋值设置成delete状态,使赋值失效
```cpp
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
title: 怎么将一个uniqe_ptr赋给另一个uniqe_ptr?
因为`uniqe_ptr`支持移动语义,因此我们使用`move`来转移对象
```cpp
    unique_ptr<A> uA1(new A);
    //unique_ptr<A> uA2 = uA1; error 
    unique_ptr<A> uA2 = move(uA1);
    //unique_ptr<A> uA3(uA2);error 
    unique_ptr<A> uA3(move(uA2));