p2._use_count()">
当前位置:   article > 正文

智能指针(unique_ptr、shared_ptr、weak_ptr)_use_count()

use_count()

主要参考链接:C++ 智能指针最佳实践&源码分析

参考链接:C++11 make_shared - 简书

智能指针在 C++11 标准中被引入真正的标准库(C++98 中引入的 auto_ptr 存在较多问题)

为什么需要智能指针? 

解决动态内存分配时的内存泄露问题。

auto_ptr(C++98的方案,C++11已经抛弃)

  1. auto_ptr<string> p1 (new string ("abc"));
  2. auto_ptr<string> p2;
  3. p2 = p1; //auto_ptr不会报错

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!

unique_ptr

unique_ptr的核心特点是拥有对持有对象的唯一所有权。即两个unique_ptr不能同时指向同一个对象。

那具体这个唯一所有权如何体现呢?

1、unique_ptr不能被复制到另外一个unique_ptr,不能拷贝构造和赋值。

2、unique_ptr所持有的对象只能通过转移语义将所有权转移到另外一个unique_ptr

  1. std::unique_ptr<A> a1(new A());
  2. std::unique_ptr<A> a2 = a1;//编译报错,不允许复制
  3. std::unique_ptr<A> a3 = std::move(a1);//可以转移所有权,所有权转义后a1不再拥有任何指针

智能指针有一个通用的规则,就是->表示用于调用指针原有的方法,而.则表示调用智能指针本身的方法。

unique_ptr本身拥有的方法主要包括:

1、get() 获取其保存的原生指针,尽量不要使用

2、bool() 判断是否拥有指针

3、release() 释放所管理指针的所有权,返回原生指针。但并不销毁原生指针。

4、reset() 释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针

  1. std::unique_ptr<A> a1(new A());
  2. A *origin_a = a1.get();//尽量不要暴露原生指针
  3. if(a1)
  4. {
  5.     // a1 拥有指针
  6. }
  7. std::unique_ptr<A> a2(a1.release());//常见用法,转义拥有权
  8. a2.reset(new A());//释放并销毁原有对象,持有一个新对象
  9. a2.reset();//释放并销毁原有对象,等同于下面的写法
  10. a2 = nullptr;//释放并销毁原有对象

shared_ptr

shared_ptr强调的是共享所有权。也就是说多个shared_ptr可以拥有同一个原生指针的所有权。

  1. std::shared_ptr<A> a1(new A());
  2. std::shared_ptr<A> a2 = a1;//编译正常,允许所有权的共享

shared_ptr 是通过引用计数的方式管理指针,当引用计数为 0 时会销毁拥有的原生对象。

shared_ptr本身拥有的方法主要包括:

1、get() 获取其保存的原生指针,尽量不要使用

2、bool() 判断是否拥有指针

3、reset() 释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针

4、unique() 如果引用计数为 1,则返回 true,否则返回 false

5、use_count() 返回引用计数的大小

  1. std::shared_ptr<A> a1(new A());
  2. std::shared_ptr<A> a2 = a1;//编译正常,允许所有权的共享
  3. A *origin_a = a1.get();//尽量不要暴露原生指针
  4. if(a1)
  5. {
  6.     // a1 拥有指针
  7. }
  8. if(a1.unique())
  9. {
  10.     // 如果返回true,引用计数为1
  11. }
  12. long a1_use_count = a1.use_count();//引用计数数量

weak_ptr

weak_ptr 比较特殊,它主要是为了配合shared_ptr而存在的。就像它的名字一样,它本身是一个弱指针,因为它本身是不能直接调用原生指针的方法的。如果想要使用原生指针的方法,需要将其先转换为一个shared_ptr。那weak_ptr存在的意义到底是什么呢?

由于shared_ptr是通过引用计数来管理原生指针的,那么最大的问题就是循环引用(比如 a 对象持有 b 对象,b 对象持有 a 对象),这样必然会导致内存泄露。而weak_ptr不会增加引用计数,因此将循环引用的一方修改为弱引用,可以避免内存泄露。

weak_ptr可以通过一个shared_ptr创建。

  1. std::shared_ptr<A> a1(new A());
  2. std::weak_ptr<A> weak_a1 = a1;//不增加引用计数

weak_ptr本身拥有的方法主要包括:

1、expired() 判断所指向的原生指针是否被释放,如果被释放了返回 true,否则返回 false

2、use_count() 返回原生指针的引用计数

3、lock() 返回 shared_ptr,如果原生指针没有被释放,则返回一个非空的 shared_ptr,否则返回一个空的 shared_ptr

4、reset() 将本身置空

  1. std::shared_ptr<A> a1(new A());
  2. std::weak_ptr<A> weak_a1 = a1;//不增加引用计数
  3. if(weak_a1.expired())
  4. {
  5.     //如果为true,weak_a1对应的原生指针已经被释放了
  6. }
  7. long a1_use_count = weak_a1.use_count();//引用计数数量
  8. if(std::shared_ptr<A> shared_a = weak_a1.lock())
  9. {
  10.     //此时可以通过shared_a进行原生指针的方法调用
  11. }
  12. weak_a1.reset();//将weak_a1置空

unique_ptr 的使用场景

unique_ptr一般在不需要多个指向同一个对象的指针时使用。但这个条件本身就很难判断,在我看来可以简单的理解:这个对象在对象或方法内部使用时优先使用unique_ptr

1、对象内部使用

  1. class TestUnique
  2. {
  3. private:
  4.     std::unique_ptr<A> a_ = std::unique_ptr<A>(new A());
  5. public:
  6.     void process1()
  7.     {
  8.         a_->do_something();
  9.     }
  10.     void process2()
  11.     {
  12.         a_->do_something();
  13.     }
  14.     ~TestUnique()
  15.     {
  16.         //此处不再需要手动删除a_
  17.     }
  18. };

2、方法内部使用

  1. void test_unique_ptr()
  2. {
  3.     std::unique_ptr<A> a(new A());
  4.     a->do_something();
  5. }

3.1.2 shared_ptr 的使用场景及最佳实践

shared_ptr一般在需要多个执行同一个对象的指针使用。在我看来可以简单的理解:这个对象需要被多个 Class 同时使用的时候。

  1. class B
  2. {
  3. private:
  4.     std::shared_ptr<A> a_;
  5. public:
  6.     B(std::shared_ptr<A>& a): a_(a) {}
  7. };
  8. class C
  9. {
  10. private:
  11.     std::shared_ptr<A> a_;
  12. public:
  13.     C(std::shared_ptr<A>& a): a_(a) {}
  14. };
  15. std::shared_ptr<B> b_;
  16. std::shared_ptr<C> c_;
  17. void test_A_B_C()
  18. {
  19.     std::shared_ptr<A> a = std::make_shared<A>();
  20.     b_ = std::make_shared<B>(a);
  21.     c_ = std::make_shared<C>(a);
  22. }

在上面的代码中需要注意,我们使用std::make_shared代替new的方式创建shared_ptr

为什么使用std::make_shared ?

因为使用new的方式创建shared_ptr会导致出现两次内存申请,而std::make_shared在内部实现时只会申请一个内存。因此建议后续均使用std::make_shared

std::make_shared(比起直接使用new)的一个特性是能提升效率。如果直接使用new的代码:

std::shared_ptr<Widget> vpt(new Widget);

这段代码需要分配内存,但是它实际上要分配两次。每个std::shared_ptr都指向一个控制块,控制块包含被指向对象的引用计数以及其他东西。这个控制块的内存是在std::shared_ptr的构造函数中分配的。因此直接使用new,需要一块内存分配给Widget,还要一块内存分配给控制块。

如果使用std::make_shared来替换

auto spw = std::make_shared<Widget>();

一次分配就足够了。这是因为std::make_shared申请一个单独的内存块来同时存放Widget对象和控制块。这个优化减少了程序的静态大小,因为代码只包含一次内存分配的调用,并且这会加快代码的执行速度,因为内存只分配了一次。另外,使用std::make_shared消除了一些控制块需要记录的信息,这样潜在地减少了程序的总内存占用。

如果A想要调用BC的方法怎么办呢?可否在A中定义BCshared_ptr呢?答案是不可以,这样会产生循环引用,导致内存泄露。

此时就需要weak_ptr出场了。

  1. class A
  2. {
  3. private:
  4.     std::weak_ptr<B> b_;
  5.     std::weak_ptr<C> c_;
  6. public:
  7.     void do_something() {}
  8.     void set_B_C(const std::shared_ptr<B>& b, const std::shared_ptr<C>& c)
  9.     {
  10.         b_ = b;
  11.         c_ = c;
  12.     }
  13. };
a->set_B_C(b_, c_);

如果想要在A内部将当前对象的指针共享给其他对象,需要怎么处理呢?

  1. class D
  2. {
  3. private:
  4.     std::shared_ptr<A> a_;
  5. public:
  6.     std::shared_ptr<A>& a): a_(a) {}
  7. };
  8. class A
  9. {
  10. //上述代码省略
  11. public:
  12.     void new_D()
  13.     {
  14.         //错误方式,用this指针重新构造shared_ptr,将导致二次释放当前对象
  15.         std::shared_ptr<A> this_shared_ptr1(this);
  16.         std::unique_ptr<D> d1(new D(this_shared_ptr1));
  17.     }
  18. };

如果采用this指针重新构造shared_ptr是肯定不行的,因为重新创建的shared_ptr与当前对象的shared_ptr没有关系,没有增加当前对象的引用计数。这将导致任何一个shared_ptr计数为 0 时提前释放了对象,后续操作这个释放的对象都会导致程序异常。

此时就需要引入shared_from_this。对象继承了enable_shared_from_this后,可以通过shared_from_this()获取当前对象的shared_ptr指针。

  1. class A: public std::enable_shared_from_this<A>
  2. {
  3. //上述代码省略
  4. public:
  5.     void new_D()
  6.     {
  7.         //错误方式,用this指针重新构造shared_ptr,将导致二次释放当前对象
  8.         std::shared_ptr<A> this_shared_ptr1(this);
  9.         std::unique_ptr<D> d1(new D(this_shared_ptr1));
  10.         //正确方式
  11.         std::shared_ptr<A> this_shared_ptr2 = shared_from_this();
  12.         std::unique_ptr<D> d2(new D(this_shared_ptr2));
  13.     }
  14. };

智能指针的错误用法

智能指针的使用时有较多常见的错误用法,可能会导致程序异常。下面我会列举这些错误用法,开发时需要避免。

1、使用智能指针托管的对象,尽量不要在再使用原生指针

很多开发同学(包括我在内)在最开始使用智能指针的时候,对同一个对象会混用智能指针和原生指针,导致程序异常。

  1. void incorrect_smart_pointer1()
  2. {
  3.     A *a= new A();
  4.     std::unique_ptr<A> unique_ptr_a(a);
  5.     // 此处将导致对象的二次释放
  6.     delete a;
  7. }

2、不要把一个原生指针交给多个智能指针管理

如果将一个原生指针交个多个智能指针,这些智能指针释放对象时会产生对象的多次销毁

  1. void incorrect_smart_pointer2()
  2. {
  3.     A *a= new A();
  4.     std::unique_ptr<A> unique_ptr_a1(a);
  5.     std::unique_ptr<A> unique_ptr_a2(a);// 此处将导致对象的二次释放
  6. }

3、尽量不要使用 get()获取原生指针

  1. void incorrect_smart_pointer3()
  2. {
  3.     std::shared_ptr<A> shared_ptr_a1 = std::make_shared<A>();
  4.     A *a= shared_ptr_a1.get();
  5.     std::shared_ptr<A> shared_ptr_a2(a);// 此处将导致对象的二次释放
  6.     delete a;// 此处也将导致对象的二次释放
  7. }

4、不要将 this 指针直接托管智能指针

  1. class E
  2. {
  3.     void use_this()
  4.     {
  5.         //错误方式,用this指针重新构造shared_ptr,将导致二次释放当前对象
  6.         std::shared_ptr<E> this_shared_ptr1(this);
  7.     }
  8. };
std::shared_ptr<E> e = std::make_shared<E>();

5、智能指针只能管理堆对象,不能管理栈上对象

栈上对象本身在出栈时就会被自动销毁,如果将其指针交给智能指针,会造成对象的二次销毁

  1. void incorrect_smart_pointer5()
  2. {
  3.     int int_num = 3;
  4.     std::unique_ptr<int> int_unique_ptr(&amp;int_num);
  5. }

总结: 

1、unique_ptr独占对象的所有权,由于没有引用计数,因此性能较好

2、shared_ptr共享对象的所有权,但性能略差

3、weak_ptr配合shared_ptr,解决循环引用的问题

由于性能问题,那么可以粗暴的理解:优先使用unique_ptr。但由于unique_ptr不能进行复制,因此部分场景下不能使用的。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/206260
推荐阅读
相关标签
  

闽ICP备14008679号