服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C语言智能指针之weak_ptr浅析

C语言智能指针之weak_ptr浅析

2022-01-24 14:31AlbertS C/C++

这篇文章主要介绍了 C++11智能指针之weak_ptr详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

前言

weak_ptr这个指针天生一副“小弟”的模样,也是在C++11的时候引入的标准库,它的出现完全是为了弥补它老大shared_ptr天生有缺陷的问题,其实相比于上一代的智能指针auto_ptr来说,新进老大shared_ptr可以说近乎完美,但是通过引用计数实现的它,虽然解决了指针独占的问题,但也引来了引用成环的问题,这种问题靠它自己是没办法解决的,所以在C++11的时候将shared_ptr和weak_ptr一起引入了标准库,用来解决循环引用的问题。

weak_ptr本身也是一个模板类,但是不能直接用它来定义一个智能指针的对象,只能配合shared_ptr来使用,可以将shared_ptr的对象赋值给weak_ptr,并且这样并不会改变引用计数的值。查看weak_ptr的代码时发现,它主要有lock、swap、reset、expired、operator=、use_count几个函数,与shared_ptr相比多了lock、expired函数,但是却少了get函数,甚至连operator* 和 operator->都没有,可用的函数数量少的可怜,下面通过一些例子来了解一下weak_ptr的具体用法。

 

使用环境

1.VS2015 + Windows7(应该是C++11标准)

2.头文件#include <memory>

3.命名空间using namespace std;

 

测试过程

1.weak_ptr解决shared_ptr循环引用的问题

定义两个类,每个类中又包含一个指向对方类型的智能指针作为成员变量,然后创建对象,设置完成后查看引用计数后退出,看一下测试结果:

class CB;
class CA
{
public:
  CA() { cout << "CA() called! " << endl; }
  ~CA() { cout << "~CA() called! " << endl; }
  void set_ptr(shared_ptr<CB>& ptr) { m_ptr_b = ptr; }
  void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
  void show() { cout << "this is class CA!" << endl; }
private:
  shared_ptr<CB> m_ptr_b;
};
class CB
{
public:
  CB() { cout << "CB() called! " << endl; }
  ~CB() { cout << "~CB() called! " << endl; }
  void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
  void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
  void show() { cout << "this is class CB!" << endl; }
private:
  shared_ptr<CA> m_ptr_a;
};
void test_refer_to_each_other()
{
  shared_ptr<CA> ptr_a(new CA());
  shared_ptr<CB> ptr_b(new CB());
  cout << "a use count : " << ptr_a.use_count() << endl;
  cout << "b use count : " << ptr_b.use_count() << endl;
  ptr_a->set_ptr(ptr_b);
  ptr_b->set_ptr(ptr_a);
  cout << "a use count : " << ptr_a.use_count() << endl;
  cout << "b use count : " << ptr_b.use_count() << endl;
}

测试结果如下:

CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 2
b use count : 2

通过结果可以看到,最后CA和CB的对象并没有被析构,其中的引用效果如下图所示,起初定义完ptr_a和ptr_b时,只有①③两条引用,然后调用函数set_ptr后又增加了②④两条引用,当test_refer_to_each_other这个函数返回时,对象ptr_a和ptr_b被销毁,也就是①③两条引用会被断开,但是②④两条引用依然存在,每一个的引用计数都不为0,结果就导致其指向的内部对象无法析构,造成内存泄漏。

C语言智能指针之weak_ptr浅析

解决这种状况的办法就是将两个类中的一个成员变量改为weak_ptr对象,因为weak_ptr不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将CB中的成员变量改为weak_ptr对象,代码如下:

class CB
{
public:
  CB() { cout << "CB() called! " << endl; }
  ~CB() { cout << "~CB() called! " << endl; }
  void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
  void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
  void show() { cout << "this is class CB!" << endl; }
private:
  weak_ptr<CA> m_ptr_a;
};

测试结果如下:

CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 1
b use count : 2
~CA() called!
~CB() called!

通过这次结果可以看到,CA和CB的对象都被正常的析构了,引用关系如下图所示,流程与上一例子相似,但是不同的是④这条引用是通过weak_ptr建立的,并不会增加引用计数,也就是说CA的对象只有一个引用计数,而CB的对象只有2个引用计数,当test_refer_to_each_other这个函数返回时,对象ptr_a和ptr_b被销毁,也就是①③两条引用会被断开,此时CA对象的引用计数会减为0,对象被销毁,其内部的m_ptr_b成员变量也会被析构,导致CB对象的引用计数会减为0,对象被销毁,进而解决了引用成环的问题。

C语言智能指针之weak_ptr浅析

2. 测试weak_ptr对引用计数的影响

其实weak_ptr本身设计的很简单,就是为了辅助shared_ptr的,它本身不能直接定义指向原始指针的对象,只能指向shared_ptr对象,同时也不能将weak_ptr对象直接赋值给shared_ptr类型的变量,最重要的一点是赋值给它不会增加引用计数:

void test1()
{
  // 编译错误 // error C2665: “std::weak_ptr<CA>::weak_ptr”: 3 个重载中没有一个可以转换所有参数类型
  // weak_ptr<CA> ptr_1(new CA());
  shared_ptr<CA> ptr_1(new CA());
  cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 1
  shared_ptr<CA> ptr_2 = ptr_1;
  cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
  cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2
  weak_ptr<CA> wk_ptr = ptr_1;
  cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
  cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2
  // 编译错误
  // error C2440 : “初始化”: 无法从“std::weak_ptr<CA>”转换为“std::shared_ptr<CA>”
  // shared_ptr<CA> ptr_3 = wk_ptr;
}

测试weak_ptr常用函数的用法

weak_ptr中只有函数lock和expired两个函数比较重要,因为它本身不会增加引用计数,所以它指向的对象可能在它用的时候已经被释放了,所以在用之前需要使用expired函数来检测是否过期,然后使用lock函数来获取其对应的shared_ptr对象,然后进行后续操作:

void test2()
{
  shared_ptr<CA> ptr_a(new CA());     // 输出:CA() called!
  shared_ptr<CB> ptr_b(new CB());     // 输出:CB() called!
  cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
  cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
  weak_ptr<CA> wk_ptr_a = ptr_a;
  weak_ptr<CB> wk_ptr_b = ptr_b;
  if (!wk_ptr_a.expired())
  {
      wk_ptr_a.lock()->show();        // 输出:this is class CA!
  }
  if (!wk_ptr_b.expired())
  {
      wk_ptr_b.lock()->show();        // 输出:this is class CB!
  }
  // 编译错误
  // 编译必须作用于相同的指针类型之间
  // wk_ptr_a.swap(wk_ptr_b);         // 调用交换函数
  wk_ptr_b.reset();                   // 将wk_ptr_b的指向清空
  if (wk_ptr_b.expired())
  {
      cout << "wk_ptr_b is invalid" << endl;  // 输出:wk_ptr_b is invalid 说明改指针已经无效
  }
  wk_ptr_b = ptr_b;
  if (!wk_ptr_b.expired())
  {
      wk_ptr_b.lock()->show();        // 输出:this is class CB! 调用赋值操作后,wk_ptr_b恢复有效
  }
  // 编译错误
  // 编译必须作用于相同的指针类型之间
  // wk_ptr_b = wk_ptr_a;

  // 最后输出的引用计数还是1,说明之前使用weak_ptr类型赋值,不会影响引用计数
  cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
  cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
}

 

现象分析

引用计数的出现,解决了对象独占的问题,但是也带来了循环引用的困扰,使用weak_ptr可以打破这种循环,当你理不清引用关系的时候,不妨采用文中画图的方式来理一理头绪,或许就会有眼前一亮的感觉。

总结 weak_ptr虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock函数。weak_ptr设计之初就是为了服务于shared_ptr的,所以不增加引用计数就是它的核心功能。由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下。 测试源码

 

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容!

原文链接:https://blog.csdn.net/albertsh/article/details/82286999

延伸 · 阅读

精彩推荐
  • C/C++c/c++内存分配大小实例讲解

    c/c++内存分配大小实例讲解

    在本篇文章里小编给大家整理了一篇关于c/c++内存分配大小实例讲解内容,有需要的朋友们可以跟着学习参考下。...

    jihite5172022-02-22
  • C/C++C语言实现双人五子棋游戏

    C语言实现双人五子棋游戏

    这篇文章主要为大家详细介绍了C语言实现双人五子棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    两片空白7312021-11-12
  • C/C++c/c++实现获取域名的IP地址

    c/c++实现获取域名的IP地址

    本文给大家汇总介绍了使用c/c++实现获取域名的IP地址的几种方法以及这些方法的核心函数gethostbyname的详细用法,非常的实用,有需要的小伙伴可以参考下...

    C++教程网10262021-03-16
  • C/C++使用C++制作简单的web服务器(续)

    使用C++制作简单的web服务器(续)

    本文承接上文《使用C++制作简单的web服务器》,把web服务器做的功能稍微强大些,主要增加的功能是从文件中读取网页并返回给客户端,而不是把网页代码...

    C++教程网5492021-02-22
  • C/C++C语言main函数的三种形式实例详解

    C语言main函数的三种形式实例详解

    这篇文章主要介绍了 C语言main函数的三种形式实例详解的相关资料,需要的朋友可以参考下...

    ieearth6912021-05-16
  • C/C++关于C语言中E-R图的详解

    关于C语言中E-R图的详解

    今天小编就为大家分享一篇关于关于C语言中E-R图的详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看...

    Struggler095962021-07-12
  • C/C++OpenCV实现拼接图像的简单方法

    OpenCV实现拼接图像的简单方法

    这篇文章主要为大家详细介绍了OpenCV实现拼接图像的简单方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    iteye_183805102021-07-29
  • C/C++深入C++拷贝构造函数的总结详解

    深入C++拷贝构造函数的总结详解

    本篇文章是对C++中拷贝构造函数进行了总结与介绍。需要的朋友参考下...

    C++教程网5182020-11-30