C语言智能指针之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的代码时发现,它主要有lockswapresetexpiredoperator=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_aptr_b时,只有①③两条引用,然后调用函数set_ptr后又增加了②④两条引用,当test_refer_to_each_other这个函数返回时,对象ptr_a和ptr_b被销毁,也就是①③两条引用会被断开,但是②④两条引用依然存在,每一个的引用计数都不为0,结果就导致其指向的内部对象无法析构,造成内存泄漏。

解决这种状况的办法就是将两个类中的一个成员变量改为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!

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

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函数检测一下。 测试源码

总结

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

(0)

相关推荐

  • C++11新特性之智能指针(shared_ptr/unique_ptr/weak_ptr)

    shared_ptr基本用法 shared_ptr采用引用计数的方式管理所指向的对象.当有一个新的shared_ptr指向同一个对象时(复制shared_ptr等),引用计数加1.当shared_ptr离开作用域时,引用计数减1.当引用计数为0时,释放所管理的内存. 这样做的好处在于解放了程序员手动释放内存的压力.之前,为了处理程序中的异常情况,往往需要将指针手动封装到类中,通过析构函数来释放动态分配的内存:现在这一过程就可以交给shared_ptr去做了. 一般我们使用make_shared来

  • C++11智能指针之weak_ptr详解

    如题,我们今天要讲的是 C++11 引入的三种智能指针中的:weak_ptr. 在学习 weak_ptr 之前最好对 shared_ptr 有所了解.如果你还不知道 shared_ptr 是何物,可以看看另一篇文章: [C++11新特性] C++11智能指针之shared_ptr 1.为什么需要weak_ptr? 在正式介绍weak_ptr之前,我们先来回忆一下shared_ptr的一些知识. 我们知道shared_ptr是采用引用计数的智能指针,多个shared_ptr实例可以指向同一个动态对

  • 浅析Boost智能指针:scoped_ptr shared_ptr weak_ptr

    一. scoped_ptrboost::scoped_ptr和std::auto_ptr非常类似,是一个简单的智能指针,它能够保证在离开作用域后对象被自动释放.下列代码演示了该指针的基本应用: 复制代码 代码如下: #include <string>#include <iostream>#include <boost/scoped_ptr.hpp> class implementation{public:    ~implementation() { std::cout

  • C++11中的智能指针shared_ptr、weak_ptr源码解析

    目录 1.前言 2.源码准备 3.智能指针概念 4.源码解析 4.1.shared_ptr解析 4.1.1.shared_ptr 4.1.2.__shared_ptr 4.1.3.__shared_count 4.1.4._Sp_counted_base 4.1.5._Sp_counted_ptr 4.1.6.shared_ptr总结 4.2.weak_ptr解析 4.2.1.weak_ptr 4.2.2.__weak_ptr 4.2.3.__weak_count 4.2.4.回过头看weak_

  • C语言智能指针之weak_ptr浅析

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

  • C语言 智能指针 shared_ptr 和 weak_ptr

    weak_ptr引入可以解决shared_ptr交叉引用时无法释放资源的问题. 示例代码: #include <iostream> #include <memory> using namespace std; class B; class A{ public:     A(){cout << "A constructor ... "<< endl;}     ~A(){cout << "A destructor ..

  • C++中Boost的智能指针weak_ptr

    循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象.一个简单的例子如下: #include <string> #include <iostream> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> class parent; class children; typedef boost::shared_ptr<parent> p

  • 关于c++ 智能指针及 循环引用的问题

    c++智能指针介绍 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete,比如流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见,并造成内存泄露.如此c++引入 智能指针 ,智能指针即是C++ RAII的一种应用,可用于动态资源管理,资源即对象的管理策略. 智能指针在 <memory>标头文件的 std 命名空间中定义. 它们对 RAII 或 获取资源即初始化 编程惯用法至关重要. RAII 的主要原则是

  • c++ 智能指针基础详解

    简介 在现代 C++ 编程中,标准库包含了智能指针(Smart pointers). 智能指针用来确保程序不会出现内存和资源的泄漏,并且是"异常安全"(exception-safe)的. 智能指针的使用 智能指针定义在头文件 memory 里的命名空间 std 中.它对于资源获取即初始化(RAII, Resource Acquisition Is Initialization) 编程理念至关重要.该理念的目的是保证对象初始化的时候也是资源获取的时候,从而使对象的所有资源在单行代码中创建

  • C++学习之移动语义与智能指针详解

    移动语义 1.几个基本概念的理解 (1)可以取地址的是左值,不能取地址的就是右值,右值可能存在寄存器,也可能存在于栈上(短暂存在栈)上 (2)右值包括:临时对象.匿名对象.字面值常量 (3)const 左值引用可以绑定到左值与右值上面,称为万能引用.正因如此,也就无法区分传进来的参数是左值还是右值. const int &ref = a;//const左值引用可以绑定到左值 const int &ref1 = 10;//const左值引用可以绑定到右值 (4)右值引用:只能绑定到右值不能绑

  • C++11智能指针中的 unique_ptr实例详解

    在前面一篇文章中,我们了解了 C++11 中引入的智能指针之一 shared_ptr 和 weak_ptr ,今天,我们来介绍一下另一种智能指针 unique_ptr . 往期文章参考: [C++11新特性] C++11 智能指针之shared_ptr [C++11新特性] C++11智能指针之weak_ptr unique_ptr介绍 unique是独特的.唯一的意思,故名思议,unique_ptr可以"独占"地拥有它所指向的对象,它提供一种严格意义上的所有权. 这一点和我们前面介绍

  • C++ STL 四种智能指针的用法详解

    0.前言 C++ 标准模板库 STL(Standard Template Library) 一共给我们提供了四种智能指针:auto_ptr.unique_ptr.shared_ptr 和 weak_ptr,其中 auto_ptr 是 C++98 提出的,C++11 已将其摒弃,并提出了 unique_ptr 替代 auto_ptr.虽然 auto_ptr 已被摒弃,但在实际项目中仍可使用,但建议使用更加安全的 unique_ptr,后文会详细叙述.shared_ptr 和 weak_ptr 则是

随机推荐