C++新特性-智能指针

简介: C++新特性-智能指针

std::weak_ptr

std::weak_ptr是一种弱引用,它不能单独使用,设计之初是为了配合std::shared_ptr,解决后者设计上存在的问题。

  • 使用注意:
  • 不能直接指向原始指针:std::weak_ptr<int> wp (new int);
  • 只能指向std::shared_ptr对象或者std::weak_ptr对象
  • 不增加引用计数
  • 可以用expired()来检测指向的std::shared_ptr管理的对象是否被析构了。
  • 不能直接使用std::shared_ptr管理的对象,如果要使用需要调用lock()。如果底层的对象还没被析构,那么就会返回一个std::shared_ptr指针,指向该对象,否则返回nullptr
  • 构造函数
constexpr weak_ptr() noexcept;
    weak_ptr( const weak_ptr& r ) noexcept;
    weak_ptr( weak_ptr&& r ) noexcept;
    template< typename Y >
    weak_ptr( const weak_ptr<Y>& r ) noexcept;
    
    template< typename Y >
    weak_ptr( weak_ptr<Y>&& r ) noexcept;
    template< typename Y >
    weak_ptr( const std::shared_ptr<Y>& r ) noexcept;
  • 从构造函数可见,std::weak_ptr只能接受std::weak_ptrstd::shared_ptr类型,而不能std::weak_ptr<T> wp (new T);
    移动语义下的构造函数,构造完成 r 将会变成 nullptr ,不可用。
    std::weak_ptr 的正确使用场景是那些资源如果可能就使用,如果不可使用则不用的场景,它不参与资源的生命周期管理。例如,网络分层结构中,Session 对象(会话对象)利用 Connection 对象(连接对象)提供的服务工作,但是 Session 对象不管理 Connection 对象的生命周期,Session 管理 Connection 的生命周期是不合理的,因为网络底层出错会导致 Connection 对象被销毁,此时 Session 对象如果强行持有 Connection 对象与事实矛盾。
  • std::weak_ptr 主要有两个用途:

它只能配合std::shared_ptr使用,不能单独使用。

  • 防止 std::shared_ptr循环引用
    如果两个std::shared_ptr相互引用,那么就会形成一个环,引用计数无法变成0,也会导致内存泄漏。
class Foo : public std::enable_shared_from_this<Foo> { 
  public:
    Foo(){ std::cout<<"ctor\n"; }
    ~Foo(){ std::cout<<"dtor\n"; }
    void self() 
    { 
      fptr_ = shared_from_this();
    }
  private:
    std::shared_ptr<Foo> fptr_; // 改 fptr_ 为 std::weak_ptr 类型即可
  };
  int main() { 
    {
    std::shared_ptr<Foo> fptr = std::make_shared<Foo>();
    fptr->self();
    }   
    return 0;
  }
  • std::enable_shared_from_this<T>::shared_from_this
    这是个侵入式设计。为的解决传入this导致对象被析构两次的问题。
    什么情况下需要使用 shared_from_this() ??? 用于返回当前对象 *thisstd::shared_ptr类型指针时:
class Foo : public enable_shared_from_this<Foo>{
   public:
       Foo(){ 
           std::cout<<"Foo ctor.\n";
       }
       ~Foo(){
           std::cout<<"Foo dtor.\n";
       }
       
       std::shared_ptr<Foo> getSelf(){ 
           return shared_from_this();
       }
   };
   
   int main() {
       Foo* foo = new Foo; 
       std::shared_ptr<Foo> sp1(foo);
       std::shared_ptr<Foo> sp2 = sp1->getSelf();  // 为了对 foo对象进行共享
       
       std::cout<<std::boolalpha;
       std::cout<<(sp2.get()== foo)<<std::endl;
       std::cout<<sp1.use_count()<<std::endl;
   } 
  • 函数原型
template<typename _Tp>
   class enable_shared_from_this {
   protected:
       ...
   public:
       shared_ptr<_Tp>
       shared_from_this() { 
           return shared_ptr<_Tp>(this->_M_weak_this); 
       }
       shared_ptr<const _Tp>
       shared_from_this() const { 
           return shared_ptr<const _Tp>(this->_M_weak_this); 
       }
   private:
       ...
       mutable weak_ptr<_Tp>  _M_weak_this;
   }
  • enable_shared_from_this的子类需要返回自身的std::shared_ptr指针,那么就需要继承这个类。
  • 成员变量为什么是weak_ptr类型
    因为如果是std::shared_ptr类型,那么就永远无法析构对象自身。
    这个_M_weak_this不是这个类中初始化,而是在shared_ptr中初始化,初始化的值就是this。因此如果智能指针类型是std::shared_ptr,那么这个类对象一旦创建,引用计数就是1,那么永远也无法析构。
  • 为什么不直接传回this
    std::shared_ptr的引用计数增加是需要用operator=实现的。
class Foo {/** ... */};
    
    int main() {
        Foo* foo = new Foo;
        std::shared_ptr<Foo> sp1(foo);
        std::shared_ptr<Foo> sp2(foo);
        std::cout<<sp1.use_count()<<std::endl; // 输出是1
    } 
  • 也就是说,尽管sp1sp2都指向了foo,但是却不共享计数,当析构的时候就会被析构两次,产生未定义行为。
    std::weak_ptr可以接受std::shared_ptr参数来构造自己,std::shared_ptr也具有接受std::weak_ptr参数来构造自己。

智能指针问题

  • share_ptrunique_ptr区别
    主要在于前者采用引用技术实现对象共享,redis里的对象管理也是采用这个。而后者只能独占,不能赋值/复制,只能移动,因为其拷贝构造函数和赋值函数被禁用了。
    std::unique_ptr内部:
// Disable copy from lvalue.
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
shared_ptr 指向一个动态数组需要注意什么?

析构器需要设置为 delete[]。而uniqued_ptr的的默认析构器模板类std::default_delete,能自动设别new intnew int[]因为不用担心。

template< class Y > explicit shared_ptr( Y* ptr );
  template< class Y, class Deleter >  shared_ptr( Y* ptr, Deleter d )

上面一个,默认的析构器是delete ptr。当 Y* ptr = new Y[x],析构器也需要重置。

#include <memory>
  #include <vector>
  #include <algorithm>
  
  int main() {
  //   {
  //        std::shared_ptr<int> shared_bad(new int[10]);
  //    } // 析构函数调用 delete ,未定义行为
  
      {
          std::shared_ptr<int> shared_good(new int[10], std::default_delete<int[]>());
      } // 析构函数调用 delete[] , ok
  
      {
          std::unique_ptr<int> ptr(new int(5));
      } // unique_ptr<int> 使用 default_delete<int>
  
      {
          std::unique_ptr<int[]> ptr(new int[10]);
      } // unique_ptr<int[]> 使用 default_delete<int[]>
  }
std::shared_ptr 线程安全 ?

std::shared_ptr的引用计数本身是安全且无锁的,但对象的读写则不是。也就是说std::shared_ptr对象的创建析构是线程安全的,但是多线程读写std::shared_ptr对象不是线程安全的。std::shared_ptr 内存是由于两个组成部分: 指向管理对象的指针 和 引用计数器。在读/写时,是直接对两个变量操作,不可能是原子类型的。因为 std::shared_ptr 有两个数据成员,读写操作不能原子化.使得多线程读写同一个 std::shared_ptr 对象需要加锁.

std::weak_ptr的实现原理

std::weak_ptr 是为了解决 std::shared_ptr 循环引用而生,构造 std::weak_ptr 对象只能通过 std::shared_ptr 来构造,但是std::weak_ptr 对象的生命周期对相应的 std::shared_ptr的引用计数不产生影响,即不增加或者减少引用计数。

std::weak_ptr的引用计数部分也是有锁操作,因此 std::weak_ptr 对象生命周期的构造与销毁都是线程安全的。

// 基类
    template<typename _Tp, _Lock_policy _Lp>
    class __weak_ptr
    {
      template<typename _Yp, typename _Res = void>
    using _Compatible = typename enable_if<__sp_compatible_with<_Yp*, _Tp*>::value, _Res>::type;
      // Constraint for assignment from shared_ptr and weak_ptr:
      template<typename _Yp>
      using _Assignable = _Compatible<_Yp, __weak_ptr&>;
    public:
      using element_type = typename remove_extent<_Tp>::type;
      constexpr __weak_ptr() noexcept
      : _M_ptr(nullptr), _M_refcount()
      { }
      __weak_ptr(const __weak_ptr&) noexcept = default;
      ~__weak_ptr() = default;
      // The "obvious" converting constructor implementation:
      //
      //  template<typename _Tp1>
      //  __weak_ptr(const __weak_ptr<_Tp1, _Lp>& __r)
      //  : _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount) // never throws
      //  { }
      //
      // has a serious problem.
      //
      //  __r._M_ptr may already have been invalidated. The _M_ptr(__r._M_ptr)
      //  conversion may require access to *__r._M_ptr (virtual inheritance).
      //
      // It is not possible to avoid spurious access violations since
      // in multithreaded programs __r._M_ptr may be invalidated at any point.
      template<typename _Yp, typename = _Compatible<_Yp>>
      __weak_ptr(const __weak_ptr<_Yp, _Lp>& __r) noexcept
      : _M_refcount(__r._M_refcount)
       { _M_ptr = __r.lock().get(); }
      template<typename _Yp, typename = _Compatible<_Yp>>
      __weak_ptr(const __shared_ptr<_Yp, _Lp>& __r) noexcept
      : _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount)
      { }
      __weak_ptr(__weak_ptr&& __r) noexcept
      : _M_ptr(__r._M_ptr), _M_refcount(std::move(__r._M_refcount))
      { __r._M_ptr = nullptr; }
      template<typename _Yp, typename = _Compatible<_Yp>>
      __weak_ptr(__weak_ptr<_Yp, _Lp>&& __r) noexcept
      : _M_ptr(__r.lock().get()), _M_refcount(std::move(__r._M_refcount))
      { __r._M_ptr = nullptr; }
      __weak_ptr&
      operator=(const __weak_ptr& __r) noexcept = default;
      template<typename _Yp>
      _Assignable<_Yp>
      operator=(const __weak_ptr<_Yp, _Lp>& __r) noexcept
      {
        _M_ptr = __r.lock().get();
        _M_refcount = __r._M_refcount;
        return *this;
      }
      template<typename _Yp>
      _Assignable<_Yp>
      operator=(const __shared_ptr<_Yp, _Lp>& __r) noexcept
      {
        _M_ptr      = __r._M_ptr;
        _M_refcount = __r._M_refcount;
        return *this;
      }
      __weak_ptr&
      operator=(__weak_ptr&& __r) noexcept
      {
        _M_ptr      = __r._M_ptr;
        _M_refcount = std::move(__r._M_refcount);
        __r._M_ptr  = nullptr;
        return *this;
      }
      template<typename _Yp>
      _Assignable<_Yp>
      operator=(__weak_ptr<_Yp, _Lp>&& __r) noexcept
      {
        _M_ptr      = __r.lock().get();
        _M_refcount = std::move(__r._M_refcount);
        __r._M_ptr  = nullptr;
        return *this;
      }
      __shared_ptr<_Tp, _Lp> lock() const noexcept
      { return __shared_ptr<element_type, _Lp>(*this, std::nothrow); }
      long use_count() const noexcept
      { return _M_refcount._M_get_use_count(); }
      bool expired() const noexcept
      { return _M_refcount._M_get_use_count() == 0; }
      template<typename _Tp1>
      bool owner_before(const __shared_ptr<_Tp1, _Lp>& __rhs) const noexcept
      { return _M_refcount._M_less(__rhs._M_refcount); }
      template<typename _Tp1>
      bool owner_before(const __weak_ptr<_Tp1, _Lp>& __rhs) const noexcept
      { return _M_refcount._M_less(__rhs._M_refcount); }
      void reset() noexcept
      { __weak_ptr().swap(*this); }
      void swap(__weak_ptr& __s) noexcept
      {
        std::swap(_M_ptr, __s._M_ptr);
        _M_refcount._M_swap(__s._M_refcount);
      }
    private:
      // Used by __enable_shared_from_this.
      void _M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount) noexcept
      {
        if (use_count() == 0)
          {
            _M_ptr = __ptr;
            _M_refcount = __refcount;
          }
      }
      template<typename _Tp1, _Lock_policy _Lp1> friend class __shared_ptr;
      template<typename _Tp1, _Lock_policy _Lp1> friend class __weak_ptr;
      friend class __enable_shared_from_this<_Tp, _Lp>;
      friend class enable_shared_from_this<_Tp>;
      element_type*    _M_ptr;         // Contained pointer.
      __weak_count<_Lp>  _M_refcount;    // Reference counter.
    };
  /**
   *  @brief  A smart pointer with weak semantics.
   *
   *  With forwarding constructors and assignment operators.
   */
    template<typename _Tp>
    class weak_ptr : public __weak_ptr<_Tp>
    {
      template<typename _Arg> 
      using _Constructible = typename enable_if<is_constructible<__weak_ptr<_Tp>, _Arg>::value>::type;
      template<typename _Arg>
      using _Assignable = typename enable_if<is_assignable<__weak_ptr<_Tp>&, _Arg>::value, weak_ptr&>::type;
    public:
      constexpr weak_ptr() noexcept = default;
      template<typename _Yp,
      typename = _Constructible<const shared_ptr<_Yp>&>>
      weak_ptr(const shared_ptr<_Yp>& __r) noexcept
      : __weak_ptr<_Tp>(__r) { }
      weak_ptr(const weak_ptr&) noexcept = default;
      template<typename _Yp, typename = _Constructible<const weak_ptr<_Yp>&>>
      weak_ptr(const weak_ptr<_Yp>& __r) noexcept
      : __weak_ptr<_Tp>(__r) { }
      weak_ptr(weak_ptr&&) noexcept = default;
      template<typename _Yp, typename = _Constructible<weak_ptr<_Yp>>>
      weak_ptr(weak_ptr<_Yp>&& __r) noexcept
      : __weak_ptr<_Tp>(std::move(__r)) { }
      weak_ptr&
      operator=(const weak_ptr& __r) noexcept = default;
      template<typename _Yp>
      _Assignable<const weak_ptr<_Yp>&>
      operator=(const weak_ptr<_Yp>& __r) noexcept
      {
        this->__weak_ptr<_Tp>::operator=(__r);
        return *this;
      }
      template<typename _Yp>
      _Assignable<const shared_ptr<_Yp>&>
      operator=(const shared_ptr<_Yp>& __r) noexcept
      {
        this->__weak_ptr<_Tp>::operator=(__r);
        return *this;
      }
      weak_ptr&
      operator=(weak_ptr&& __r) noexcept = default;
      template<typename _Yp>
      _Assignable<weak_ptr<_Yp>>
      operator=(weak_ptr<_Yp>&& __r) noexcept
      {
        this->__weak_ptr<_Tp>::operator=(std::move(__r));
        return *this;
      }
      shared_ptr<_Tp> lock() const noexcept
      { return shared_ptr<_Tp>(*this, std::nothrow); }
    };
相关文章
|
1月前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
103 59
|
8天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
27 4
|
23天前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
23天前
|
存储 搜索推荐 C语言
如何理解指针作为函数参数的输入和输出特性
指针作为函数参数时,可以实现输入和输出的双重功能。通过指针传递变量的地址,函数可以修改外部变量的值,实现输出;同时,指针本身也可以作为输入,传递初始值或状态。这种方式提高了函数的灵活性和效率。
|
1月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
38 1
|
1月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
29 2
|
1月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
1月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(二)
【C++】面向对象编程的三大特性:深入解析多态机制
|
1月前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(一)
【C++】面向对象编程的三大特性:深入解析多态机制
|
26天前
|
C++
C++ 20新特性之结构化绑定
在C++ 20出现之前,当我们需要访问一个结构体或类的多个成员时,通常使用.或->操作符。对于复杂的数据结构,这种访问方式往往会显得冗长,也难以理解。C++ 20中引入的结构化绑定允许我们直接从一个聚合类型(比如:tuple、struct、class等)中提取出多个成员,并为它们分别命名。这一特性大大简化了对复杂数据结构的访问方式,使代码更加清晰、易读。
32 0