C++弱引用智能指针std::weak_ptr使用介绍

(一)引言

前面两篇博文分别介绍了智能指针的概念及分类,并且通过源代码示例讲解了std::unique_ptr和std::shared_ptr类型的智能指针。std::shared_ptr解决了多对象共享资源的内存管理问题(如工控系统中多个控制器共享数据点、视图共享数据源等场景)。但std::shared_ptr可能导致循环引用和计数无法归零,最终造成内存泄漏,std::weak_ptr可以解决这种问题。它不拥有对象的所有权,只是观察共享对象状态,既不会增加引用计数,也不影响对象的生命周期。

本文将继续结合工业软件开发场景,详细介绍std::weak_ptr核心特性、使用方法及实战案例,帮助开发者彻底掌握这一内存管理利器。

(二)std::weak_ptr核心特性

1、核心定位

std::weak_ptr无法独立管理对象,必须依附于std::shared_ptr存在。其核心特性可概括为:

  • 不拥有对象所有权,仅观测std::shared_ptr管理的对象;
  • 初始化、拷贝、赋值均不修改std::shared_ptr的引用计数;
  • 无法直接解引用(无operator*和operator->),避免访问已销毁对象;
  • 可通过成员函数安全判断对象是否存活,并获取临时的 std::shared_ptr 访问对象。

2、核心逻辑

  • 循环引用是std::shared_ptr最常见的内存泄漏场景:当两个(或多个)对象互相持有对方的std::shared_ptr时,引用计数会形成闭环,即使外部引用消失,内部引用计数仍大于0,对象无法释放。
  • std::weak_ptr的解决方案极其简洁:将循环引用链条中的任意一方替换为std::weak_ptr。由于std::weak_ptr不增加引用计数,闭环被打破,引用计数可正常归零,对象得以释放。

(三)std::weak_ptr使用方法

1、创建方法

  • std::share_ptr<int> sp_obj = std::make_share<int>(10);
  • std::weak_ptr<int> sp_obj_2(sp_obj); //从share_ptr创建
  • std::weak_ptr<int> sp_obj_3(sp_obj_2); //从已有的weak_ptr创建

2、使用方法

std::weak_ptr变量并不拥有所指向的对象,所以不能直接通过std::weak_ptr来访问对象。使用者可通过std::weak_ptr的lock()方法来获取一个临时的std::share_ptr来访问对象。但如果std::weak_ptr所指向的对象已经被销毁,lock()方法将返回一个空的std::share_ptr对象。仍采用前面的示例代码:

auto temp_share = sp_obj_2.lock();

if (NULL != temp_share)

int val_int = *temp_share;

else

;//对象被销毁

3、 成员函数

(1)lock()函数

‌避免循环引用‌:使用std::weak_ptr可避免因循环引用导致的内存泄漏。例如两个类对象互相持有对方的std::shared_ptr时,其中一个类对象可以组合另一个对象的std::weak_ptr。

‌性能考虑‌:虽然std::weak_ptr有助于管理资源,但频繁地调用lock()方法可能会影响性能,因为每次调用都需要检查对象的引用计数。因此,在需要频繁访问对象时,最好先转换为std::shared_ptr并缓存结果。

‌过期检查‌:使用lock()方法时要检查返回shared_ptr是否为空,避免访问已销毁对象。

线程安全:lock()是std::weak_ptr访问对象的唯一推荐方式,且该函数是线程安全的。

(2)expired()函数

该函数用于判断std::weak_ptr观测对象是否已销毁,等价于use_count() == 0,返回true:对象已销毁(std::weak_ptr失效),返回false:对象仍存活。

该函数仅适用于单线程或无并发修改的场景,不推荐先判活再访问(多线程下可能出现"判活为true后,对象被其他线程释放"的竞态条件)。优先使用 lock() 是唯一安全的访问方式。

(3)reset()函数

该函数用于释放std::weak_ptr对象的关联,使其变为空的弱引用对象:reset()函数调用后,expired()返回 true,lock()返回空std::shared_ptr。

该函数调用并不影响观察对象的引用计数,仅解除std::weak_ptr与对象的关联。

(4)use_count()

std::weak_ptr对象的use_count()函数返回当前对象的有效引用计数(即管理该对象的 std::shared_ptr 数量),仅用于调试/状态查看,不推荐用于业务逻辑判断(效率低+多线程不安全),std::weak_ptr本身不计入引用计数。

(四)应用案例

本应用案例中,仍采用设备及其数据对象之间的关联关系这一工业控制领域的应用场景,工业设备控制器CController和监测数据CDataPoint对象之间存在的从属和关联关系。

1、UML类图设计

控制器和监测数据对象之间的关系:控制器CController拥有多个监测数据CDataPoint对象,监测数据CDataPoint对象关联所属的控制器CController。

2、示例代码

cpp 复制代码
#include <iostream>
#include <memory>
#include <string>
#include <vector>

//##################################################################
//基类:工业对象
//##################################################################
class CIndustrialObject
{
public:
    CIndustrialObject(const std::string& strName)
        : m_strName(strName)
        , m_bEnabled(true)
    {
    }

    virtual ~CIndustrialObject()
    {
    }

    //纯虚函数,需要在派生类中实现
    virtual void Update(void) {}

protected:
    std::string m_strName;  //对象名称
    bool        m_bEnabled; //启用与否
};


//##################################################################
// 派生类:监控数据点
//##################################################################
class CDataPoint : public CIndustrialObject
{
public:
    CDataPoint(const std::string& strName, double dValue = 0.0)
        : CIndustrialObject(strName)
        , m_dValue(dValue)
        , m_strQuality("Good")
    {
    }

    virtual~CDataPoint(void)
    {
        std::cout << "Destroying CDataPoint Object: " << m_strName << std::endl;
    }

    void Update(void) override
    {
        // 模拟数据点更新
        m_dValue += 0.1;
        std::cout << "DataPoint " << m_strName << ": " << m_dValue << " (" << m_strQuality << ")" << std::endl;
    }

    void BindDevice(std::shared_ptr<CIndustrialObject> dev)
    {
        m_device = dev;
    }


private:
    double      m_dValue;     // 数据值
    std::string m_strQuality; // 数据质量

    //std::shared_ptr<CIndustrialObject> m_device; // 强引用设备,引发循环
    std::weak_ptr<CIndustrialObject> m_device; // 强引用设备,引发循环
};


//##################################################################
// 派生类:控制器设备
//##################################################################
class CController : public CIndustrialObject
{
public:
    CController(const std::string& strName)
        : CIndustrialObject(strName)
    {
    }

    virtual~CController(void)
    {
        std::cout << "Destroying CController Object: " << m_strName << std::endl;
    }

    void AddDataPoint(const std::shared_ptr<CDataPoint>& spDataPoint)
    {
        m_datas.push_back(spDataPoint);
    }

    void Update(void) override
    {
        for (const auto& dp : m_datas) {
            dp->Update();
        }
    }

private:
    typedef std::vector<std::shared_ptr<CDataPoint>> DATAPOINTS;
    DATAPOINTS      m_datas;        //数据点列表
};

int  main(int argc, char* grgv[])
{
    std::cout << "\n\n\n\n" << std::endl;
    std::cout << "main()----------start-------------------- " << std::endl;
    std::cout << "\n\n\n\n" << std::endl;

    // 模拟组态软件中创建设备与数据点

    //创建设备:循环泵
    auto dev_pump = std::make_shared<CController>("循环泵");

    //创建数据:压力和流量
    auto data_pres = std::make_shared<CDataPoint>("data_press", 0.8); //压力数据点
    auto data_flow = std::make_shared<CDataPoint>("data_flow", 2.5);  //流量数据点

    //设备和数据相互关联绑定
    dev_pump->AddDataPoint(data_pres);
    dev_pump->AddDataPoint(data_flow);
    data_pres->BindDevice(dev_pump);
    data_flow->BindDevice(dev_pump);//双向强引用,形成循环

    //lock()方法
    std::weak_ptr<CController> weak_pump = dev_pump;
    auto temp_pump = weak_pump.lock();
    if (NULL != temp_pump) {
        temp_pump->Update();
        std::cout << "\n\nweak_pump对象的使用计数 = "<< weak_pump.use_count() << std::endl;
    }

    //调用reset()释放与"循环泵"设备对象的关联
    weak_pump.reset();
    temp_pump = weak_pump.lock();
    if (NULL != temp_pump)
        temp_pump->Update();
    else {
        bool b = weak_pump.expired();
        if (b)
            std::cout << "\n\nweak_pump对象已取消与实际对象的关联!" << std::endl;
    }
    std::cout << "\n\nweak_pump对象的使用计数 = " << weak_pump.use_count() << std::endl;

    std::cout << "\n\n\n\n" << std::endl;
    std::cout << "main()----------exit-------------------- " << std::endl;
    std::cout << "\n\n\n\n" << std::endl;

    return 0;
}

3、代码说明

示例代码中,监测数据对象CDataPoint关联了"循环泵"控制器设备,起初使用了共享类型的智能指针对循环泵设备对象进行了强引用,std::shared_ptr<CIndustrialObject> m_device。

(1)设备对象和数据对象之间相互引用,导致循环引用和计数无法归零,最终造成内存泄漏,可以看到在main()函数返回时并不会调用两个对象的析构函数。经过采用std::weak_ptr类型智能指针后,避免了两类对象之间的强引用。

(2)在main()函数中,还演示了lock()、reset()、expired()、use_count()等std::weak_ptr类型智能指针的成员函数的使用。

(五)结束语

std::weak_ptr智能指针不能独立管理对象,但可解决std::shared_ptr循环引用问题,同时为系统内存数据库、观察者设计模式应用等场景提供了安全高效的解决方案,其核心要点是"弱引用、不计数、安全观测"。在实时运行的大规模应用软件系统开发中,合理搭配std::shared_ptr和std::weak_ptr类型智能指针,可有效规避内存泄漏风险,提高系统健壮性,保障稳定运行。

相关推荐
sheji34161 小时前
【开题答辩全过程】以 基于Java的智慧环卫垃圾收运管理系统设计与实现为例,包含答辩的问题和答案
java·开发语言
暮色_年华1 小时前
随想3:关于语音采集线程 使用 CFS 调度或者 SCHED_FIFO 的思考
c++
Flash.kkl2 小时前
Linux——线程的同步和互斥
linux·开发语言·c++
sunfove2 小时前
Python 面向对象编程:从过程式思维到对象模型
linux·开发语言·python
努力学习的小廉2 小时前
【QT(七)】—— 常用控件(四)
开发语言·qt
CoderCodingNo2 小时前
【GESP】C++六级考试大纲知识点梳理, (3) 哈夫曼编码与格雷码
开发语言·数据结构·c++
froginwe112 小时前
C 标准库 - `<errno.h>`
开发语言
鹿角片ljp2 小时前
Java IO流案例:使用缓冲流恢复《出师表》文章顺序
java·开发语言·windows
纵有疾風起3 小时前
【Linux 系统开发】基础开发工具详解:自动化构建、版本控制与调试器开发实战
linux·服务器·开发语言·c++·经验分享·开源·bash