经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C » 查看文章
智能指针之 weak_ptr
来源:cnblogs  作者:还在  时间:2018/9/30 11:03:24  对本文有异议

1. weak_ptr 介绍

std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性("弱")引用。在访问所引用的对象指针前必须先转换为 std::shared_ptr。 主要用来表示临时所有权,当某个对象存在时才需要被访问。转换为shared_ptr的过程等于对象的shared_ptr 的引用计数加一,因此如果你使用weak_ptr获得所有权的过程中,原来的shared_ptr被销毁,则该对象的生命期会被延长至这个临时的 std::shared_ptr 被销毁为止。 weak_ptr还可以避免 std::shared_ptr 的循环引用

std::weak_ptr简单使用:(编译系统:Linux centos 7.0 x86_64 编译器:gcc 4.8.5 )

  1. #include <memory>
  2. #include <iostream>
  3. class foo
  4. {
  5. public:
  6. foo()
  7. {
  8. std::cout << "foo construct.." << std::endl;
  9. }
  10. void method()
  11. {
  12. std::cout << "welcome Test foo.." << std::endl;
  13. }
  14. ~foo()
  15. {
  16. std::cout << "foo destruct.." << std::endl;
  17. }
  18. };
  19. int main()
  20. {
  21. // weak_ptr
  22. foo* foo2 = new foo();
  23. // share_ptr 管理对象
  24. std::shared_ptr<foo> shptr_foo2(foo2);
  25. // weak_ptr 弱引用
  26. std::weak_ptr<foo> weak_foo2(shptr_foo2);
  27. // 如果要获取数据指针,需要通过lock接口获取
  28. weak_foo2.lock()->method();
  29. std::shared_ptr<foo> tmp = weak_foo2.lock();
  30. // 我们这边有尝试多次获取所有权(lock),看一下引用计数个数
  31. std::cout << "shptr_foo2 RefCount: " << weak_foo2.lock().use_count() << std::endl;
  32. return 0;
  33. }

执行结果:

  1. bash-4.2$ ./share_ptr
  2. foo construct..
  3. welcome Test foo..
  4. shptr_foo2 RefCount: 3
  5. foo destruct..

我们可以看到,weak_ptr多次通过lock转换成shared_ptr,获得shared_ptr后可以成功调用管理对象的方法,这个过程中引用计数是在增加的,因此如果原来的shared_ptr销毁是不影响你这个临时对象使用, 资源析构正常。 下面是gnu STL 库中最后调用lock函数会跑到的地方,引用计数 + 1 。(GNU STL 文件:shared_ptr_base.h)

2. weak_ptr 实现和循环引用问题

1. shared_ptr 循环引用问题

我们首先看一下循环引用的问题,具体代码如下:

测试类:

  1. #include <memory>
  2. #include <iostream>
  3. class foo;
  4. class Test
  5. {
  6. public:
  7. Test()
  8. {
  9. std::cout << "construct.." << std::endl;
  10. }
  11. void method()
  12. {
  13. std::cout << "welcome Test.." << std::endl;
  14. }
  15. ~Test()
  16. {
  17. std::cout << "destruct.." << std::endl;
  18. }
  19. public:
  20. std::shared_ptr<foo> fooptr;
  21. };
  22. class foo
  23. {
  24. public:
  25. foo()
  26. {
  27. std::cout << "foo construct.." << std::endl;
  28. }
  29. void method()
  30. {
  31. std::cout << "welcome Test foo.." << std::endl;
  32. }
  33. ~foo()
  34. {
  35. std::cout << "foo destruct.." << std::endl;
  36. }
  37. public:
  38. std::shared_ptr<Test> testptr;
  39. };

main函数:

  1. int main()
  2. {
  3. // 循环引用 测试
  4. Test* t2 = new Test();
  5. foo* foo1 = new foo();
  6. std::shared_ptr<Test> shptr_Test(t2);
  7. std::shared_ptr<foo> shptr_foo(foo1);
  8. std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
  9. std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
  10. shptr_Test->fooptr = shptr_foo;
  11. shptr_foo->testptr = shptr_Test;
  12. std::cout << "shptr_Test RefCount: " << shptr_Test.use_count() << std::endl;
  13. std::cout << "shptr_foo RefCount: " << shptr_foo.use_count() << std::endl;
  14. return 0;
  15. }

测试结果:

  1. bash-4.2$ ./share_ptr
  2. construct..
  3. foo construct..
  4. shptr_Test RefCount: 1
  5. shptr_foo RefCount: 1
  6. shptr_Test RefCount: 2
  7. shptr_foo RefCount: 2

从打印结果可以很明显的看出,经过循环引用, 对象引用计数变成了2,并且执行完后,资源没有释放,没有调用类的destruct(析构)函数

将类中的std::shared_ptr 换成 std::weak_ptr

  1. class foo;
  2. class Test
  3. {
  4. public:
  5. Test()
  6. {
  7. std::cout << "construct.." << std::endl;
  8. }
  9. void method()
  10. {
  11. std::cout << "welcome Test.." << std::endl;
  12. }
  13. ~Test()
  14. {
  15. std::cout << "destruct.." << std::endl;
  16. }
  17. public:
  18. std::weak_ptr<foo> fooptr;
  19. };
  20. class foo
  21. {
  22. public:
  23. foo()
  24. {
  25. std::cout << "foo construct.." << std::endl;
  26. }
  27. void method()
  28. {
  29. std::cout << "welcome Test foo.." << std::endl;
  30. }
  31. ~foo()
  32. {
  33. std::cout << "foo destruct.." << std::endl;
  34. }
  35. public:
  36. std::weak_ptr<Test> testptr;
  37. };

再看下weak_ptr的执行结果,可以看到计数正常,资源成功释放

  1. bash-4.2$ ./share_ptr
  2. construct..
  3. foo construct..
  4. shptr_Test RefCount: 1
  5. shptr_foo RefCount: 1
  6. shptr_Test RefCount: 1
  7. shptr_foo RefCount: 1
  8. foo destruct..
  9. destruct..

2. weak_ptr 实现

我们这边贴一下weak_ptr类的代码:

  1. template <class T>
  2. class weak_ptr
  3. {
  4. public:
  5. template <class S>
  6. friend class weak_ptr;
  7. template <class S>
  8. friend class shared_ptr;
  9. constexpr weak_ptr() noexcept : m_iWeakRefCount(nullptr), m_ptr(nullptr) { }
  10. weak_ptr( const weak_ptr<T>& rhs ) noexcept : m_iWeakRefCount(rhs.m_iWeakRefCount)
  11. {
  12. m_ptr = rhs.lock().getPointer();
  13. }
  14. weak_ptr( const shared_ptr<T>& rhs ) noexcept
  15. : m_iWeakRefCount(rhs.m_iRefCount), m_ptr(rhs.m_ptr) { }
  16. template <typename S>
  17. weak_ptr & operator=(const shared_ptr<S> & rhs)
  18. {
  19. m_ptr = dynamic_cast<T *>(rhs.m_ptr);
  20. m_iWeakRefCount = rhs.m_iRefCount;
  21. return *this;
  22. }
  23. template <typename S>
  24. weak_ptr & operator=(const weak_ptr<S> & rhs)
  25. {
  26. m_ptr = dynamic_cast<T *>(rhs.m_ptr);
  27. m_iWeakRefCount = rhs.m_iWeakRefCount;
  28. return *this;
  29. }
  30. weak_ptr& operator=( const weak_ptr& rhs ) noexcept
  31. {
  32. m_iWeakRefCount = rhs.m_iWeakRefCount;
  33. m_ptr = rhs.m_ptr;
  34. return *this;
  35. }
  36. weak_ptr& operator=( const shared_ptr<T>& rhs ) noexcept
  37. {
  38. m_iWeakRefCount = rhs.m_iRefCount;
  39. m_ptr = rhs.m_ptr;
  40. return *this;
  41. }
  42. shared_ptr<T> lock() const noexcept
  43. {
  44. shared_ptr<T> tmp;
  45. if(m_iWeakRefCount && *m_iWeakRefCount > 0)
  46. {
  47. tmp.m_iRefCount = m_iWeakRefCount;
  48. tmp.m_ptr = m_ptr;
  49. if(tmp.m_iRefCount)
  50. {
  51. ++(*tmp.m_iRefCount);
  52. }
  53. }
  54. return tmp;
  55. }
  56. int use_count()
  57. {
  58. return *m_iWeakRefCount;
  59. }
  60. bool expired() const noexcept
  61. {
  62. return *m_iWeakRefCount == 0;
  63. }
  64. void Reset()
  65. {
  66. m_ptr = NULL;
  67. m_iWeakRefCount = NULL;
  68. }
  69. private:
  70. int * m_iWeakRefCount;
  71. T* m_ptr;
  72. };

主要注意的是lock函数,如果计数指针为空,那么会返回一个空的shared_ptr,然后就是不能重载operator*和operator-> 操作符

主要参考: cppreference.com
完整实现见:smart_ptr

3. enable_shared_from_this

这边还有一个点也要介绍一下,那就是enable_shared_from_this,这个主要是为了处理在shared_ptr管理的对象中要使用该对象的指针所引出的问题。 我们看下下面这个例子:

  1. class foo
  2. {
  3. public:
  4. std::shared_ptr<foo> getptr()
  5. {
  6. // 如果类中要返回自己的指针怎么办?
  7. return std::shared_ptr<foo>(this);
  8. }
  9. ~foo()
  10. {
  11. std::cout << "foo destruct .. " << std::endl;
  12. }
  13. };
  14. int main()
  15. {
  16. std::shared_ptr<foo> bp1(new foo());
  17. bp1->getptr();
  18. std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
  19. }

看下结果,释放两次

  1. ash-4.2$ ./share_ptr
  2. foo destruct ..
  3. bp1.use_count() = 1
  4. foo destruct ..

其实我们都不用测试,因为你如果直接使用该对象的this指针又拷贝给另一个shared_ptr,那不就等于两个没有关系的shared_ptr管理同一个对象了吗? 释放的时候等于会调用两次该对象的析构函数。enable_shared_from_this就是用来解决这个问题的。看下代码:

  1. class foo : public std::enable_shared_from_this<foo>
  2. {
  3. public:
  4. std::shared_ptr<foo> getptr()
  5. {
  6. return shared_from_this();
  7. }
  8. ~foo()
  9. {
  10. std::cout << "foo destruct .. " << std::endl;
  11. }
  12. };
  13. int main()
  14. {
  15. std::shared_ptr<foo> bp1(new foo());
  16. bp1->getptr();
  17. std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
  18. }

看下结果,成功释放:

  1. bash-4.2$ ./share_ptr
  2. bp1.use_count() = 1
  3. foo destruct ..

总结一下,weak_ptr本质是以一种观察者的形象存在,它可以获取到观察主体的状态,但是无法获取直接获取到观察主体,无法直接对观察主体修改,无法释放观察主体的资源,你只能通过转换成shared_ptr来做一些事情。 和观察者模式很像,订阅,得到观察主体状态,在多线程环境下会比较管用!

2018年9月30日00:40:02

 友情链接:直通硅谷  点职佳

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号