C++共享型智能指针std::shared_ptr使用介绍
(一)引言
前篇博文介绍了智能指针的概念及分类,并且通过源代码示例讲解了std::unique_ptr类型的智能指针。今天主要介绍共享类型的智能指针std::shared_ptr。std::shared_ptr是一种共享类型的智能指针,用于管理动态分配的对象,该对象可能被多个对象进行聚合,它采用了引用计数机制来自动释放资源。当多个std::shared_ptr指向同一个对象时,引用计数会递增;当其中一个std::shared_ptr被销毁或指向其他对象时,引用计数会递减;当引用计数变为0时,对象会被自动删除。这种机制避免了内存泄漏,提高了软件代码的安全性。
本文今天描述了两个案例,在工控组态软件中的两种不同场景,设备和数据的关系、视图和数据的关系,应用共享型智能指针后的效果。
(二)应用案例1
1、案例描述
该案例为工控组态软件的简单示例,展示了共享类型智能指针的std::shared_ptr的使用。这里面涉及到工控对象基类、设备控制器、监控数据点、系统管理器等多个类对象,其UML类图如下图所示,为了更好地体现共享类型智能指针的作用,在本章示例代码中,泵和罐两种设备控制器聚合了相同的两个数据点(温度、压力)。可能示例不太恰当,读者只要能够真正理解共享型智能指针就行。
(1)设备控制器和数据点都继承自工控对象虚基类;
(2)设备控制器聚合了多个数据点;
(3)系统管理器聚合了多个设备控制器。
2、示例代码
#include <iostream>
#include <memory>
#include <string>
#include <vector>//##################################################################
//基类:工业对象
//##################################################################
class CIndustrialObject
{
public:CIndustrialObject(const std::string& strName): m_strName(strName), m_bEnabled(true){}virtual ~CIndustrialObject(){std::cout << "Destroying Industrial Object: " << m_strName << std::endl;}//纯虚函数,需要在派生类中实现virtual void Update(void) = 0;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"){}void Update(void) override{// 模拟数据点更新m_dValue += 0.1;std::cout << "DataPoint " << m_strName << ": " << m_dValue << " (" << m_strQuality << ")" << std::endl;}private:double m_dValue; // 数据值std::string m_strQuality; // 数据质量
};//##################################################################
// 派生类:控制器设备
//##################################################################
class CController : public CIndustrialObject
{
public:CController(const std::string& strName): CIndustrialObject(strName), m_nCycleCount(0){}void AddDataPoint(const std::shared_ptr<CDataPoint>& spDataPoint){m_datas.push_back(spDataPoint);}void Update(void) override{// 模拟控制器逻辑m_nCycleCount++;std::cout << "Controller " << m_strName << " cycle " << m_nCycleCount << std::endl;// 更新所有关联的数据点for (const auto& spDataPoint : m_datas){spDataPoint->Update();}}private:typedef std::vector<std::shared_ptr<CDataPoint>> DATAPOINTS;DATAPOINTS m_datas; //数据点列表int m_nCycleCount; //控制周期计数
};//##################################################################
//系统管理器
//##################################################################class CSystemManager
{
public:CSystemManager(void){}~CSystemManager(void){}void AddObject(const std::shared_ptr<CIndustrialObject>& spObject){m_objects.push_back(spObject);}//周期性执行void RunSystemCycle(void){// 运行一个系统周期,更新所有对象for (const auto& obj : m_objects){obj->Update();}}private:CSystemManager(const CSystemManager& r);CSystemManager& operator = (CSystemManager& r);private:typedef std::vector<std::shared_ptr<CIndustrialObject>> INDUST_OBJS;INDUST_OBJS m_objects; //工业对象列表
};//##################################################################
//main()
//##################################################################
int main(int argc, char * argv[])
{//创建控制器设备auto controller_pump = std::make_shared<CController>("Pump MainController");auto controller_tank = std::make_shared<CController>("Tank MainController");//创建数据点auto temp_sensor = std::make_shared<CDataPoint>("TemperatureSensor", 25.0);auto press_sensor = std::make_shared<CDataPoint>("PressureSensor", 100.0);//添加两个数据点到[泵]控制器controller_pump->AddDataPoint(temp_sensor);controller_pump->AddDataPoint(press_sensor);//添加两个数据点到[罐]控制器controller_tank->AddDataPoint(temp_sensor);controller_tank->AddDataPoint(press_sensor);//创建系统管理器,并将控制器设备添加到系统管理器CSystemManager sys_manager;sys_manager.AddObject(controller_pump);sys_manager.AddObject(controller_tank);//周期运行几次std::cout << "\n\n\n===================================================" << std::endl;for (int i = 0; i < 3; ++i){std::cout << "\n" << std::endl;std::cout << "System Manager Cycle Run Count:" << i + 1 << std::endl;sys_manager.RunSystemCycle();std::cout << "\n" << std::endl;}std::cout << "===================================================\n\n\n" << std::endl;//注意:当main函数结束时,所有shared_ptr将超出作用域,//引用计数变为0,所有对象将被自动释放。return 0;
}
3、智能指针使用
在以上的代码示例中,共享类型智能指针的使用情况如下:
(1)在CController类中,m_datas使用std::shared_ptr<CDataPoint>存储数据点,允许多个控制器共享同一个数据点。
(2)在CSystemManager类中,m_objects使用std::shared_ptr<CIndustrialObject>存储各种工业对象,实现多态。
(3)在main函数中,使用std::make_shared创建对象,自动管理内存,避免内存泄漏。
4、程序运行结果
运行程序后,输出将显示系统周期、控制器更新和数据点更新的信息,最后显示对象销毁的信息。由于std::shared_ptr的使用,所有对象都会被正确释放。程序命令行输出结果如下图:
(三)应用案例2
1、案例描述
本章案例展示了在工控软件系统的人机界面中多个视图如何安全地共享同一数据源,文档视图如何关联同一个监视数据并进行同步刷新。介绍了共享型智能指针std::shared_ptr确保所有数据在被相关视图引用并渲染刷新,最终数据对象不被视图引用时进行自动释放。
(1)共享数据管理:CMonitorData对象通过std::shared_ptr被多个视图共享,数据更新自动反映在所有关联视图中。
(2)视图抽象:CAbstractView定义统一抽象接口,CTableView和CChartView提供不同可视化方式。
(3)文档管理:CDataDocument聚合多个视图,负责协调视图的渲染刷新。
(4)内存安全:智能指针自动管理对象的生命周期,引用计数确保数据在所有视图释放后才被销毁。
2、案例代码
#include <iostream>
#include <memory>
#include <string>
#include <vector>//前置声明
class CMonitorData;
class CAbstractView;
class CDataDocument;//监视数据类
class CMonitorData
{
public:CMonitorData(const std::string& strName, double dValue = 0.0): m_strName(strName), m_dValue(dValue){}void SetValue(double dValue){m_dValue = dValue;std::cout << "Data [" << m_strName << "] updated to: " << m_dValue << std::endl;}double GetValue(void) const{return m_dValue;}std::string GetName(void) const{return m_strName;}private:std::string m_strName; // 数据名称double m_dValue; // 数据值
};//抽象视图类
class CAbstractView
{
public:CAbstractView(const std::string& strTitle): m_strTitle(strTitle){}virtual ~CAbstractView(void) = default;std::string GetTitle(void) const{return m_strTitle;}void AddData(std::shared_ptr<CMonitorData> data){m_datas.push_back(data);}//渲染视图virtual void Render(void) const = 0;private:std::string m_strTitle; //视图标题protected:typedef std::vector<std::shared_ptr<CMonitorData>> DATAS;DATAS m_datas; //监视数据
};//表格视图类
class CTableView : public CAbstractView
{
public:CTableView(const std::string& strTitle): CAbstractView(strTitle){}void Render(void) const override{//绘制表头std::cout << "\n=== " << GetTitle() << " ===" << std::endl;std::cout << "|-------------|-------|" << std::endl;std::cout << "| Name | Value |" << std::endl;std::cout << "|-------------|-------|" << std::endl;//绘制数据for (const auto & data : m_datas) {std::cout << "| " << data->GetName() << " | " << data->GetValue() << " |" << std::endl;std::cout << "|-------------|-------|" << std::endl;}}
};//图表视图类
class CChartView : public CAbstractView
{
public:CChartView(const std::string& strTitle): CAbstractView(strTitle){}void Render(void) const override{//绘制每个柱状图for (const auto& dp : m_datas) {std::cout << "\n=== " << GetTitle() << " ===" << std::endl;std::cout << "Data: " << dp->GetName() << " = " << dp->GetValue() << std::endl;//简单图表模拟int nBars = static_cast<int>(dp->GetValue() / 10.0);std::cout << "Graph: ";for (int i = 0; i < nBars; ++i)std::cout << "*";}std::cout << std::endl;}
};//数据文档类
class CDataDocument
{
public:void AddView(std::shared_ptr<CAbstractView> spView){m_views.push_back(spView);std::cout << "Added view: " << spView->GetTitle() << std::endl;}void RenderAllViews(void) const{std::cout << "\nRendering all views..." << std::endl;for (const auto& vw : m_views)vw->Render();}private:typedef std::vector<std::shared_ptr<CAbstractView>> VIEWS;VIEWS m_views; // 视图列表
};//main
int main(int argc, char * argv[])
{//创建监视数据auto dp_temperature = std::make_shared<CMonitorData>("Temperature", 25.5);auto dp_pressure = std::make_shared<CMonitorData>("Pressure ", 100.2);//创建数据文档CDataDocument doc;//创建并添加视图(多个视图关联同一数据)auto table_view_1 = std::make_shared<CTableView>("Temperature Table");table_view_1->AddData(dp_temperature);table_view_1->AddData(dp_pressure);auto table_view_2 = std::make_shared<CTableView>("Pressure Table");table_view_2->AddData(dp_temperature);table_view_2->AddData(dp_pressure);auto chart_view_1 = std::make_shared<CChartView>("Temperature Chart");chart_view_1->AddData(dp_temperature);chart_view_1->AddData(dp_pressure);auto chart_view_2 = std::make_shared<CChartView>("Pressure Chart");chart_view_2->AddData(dp_temperature);chart_view_2->AddData(dp_pressure);doc.AddView(table_view_1);doc.AddView(table_view_2);doc.AddView(chart_view_1);doc.AddView(chart_view_2);//渲染所有视图std::cout << "\n\n\n===================================================" << std::endl;doc.RenderAllViews();std::cout << "===================================================\n\n\n" << std::endl;//更新数据(模拟实时监控)dp_temperature->SetValue(51.6);dp_pressure->SetValue(40.3);//再次渲染所有视图std::cout << "\n\n\n===================================================" << std::endl;doc.RenderAllViews();std::cout << "===================================================\n\n\n" << std::endl;//检查引用计数std::cout << "\n\n\n===================================================" << std::endl;std::cout << "\nShared_ptr use counts:" << std::endl;std::cout << "Temperature: " << dp_temperature.use_count() << std::endl;std::cout << "Pressure: " << dp_pressure.use_count() << std::endl;std::cout << "===================================================\n\n\n" << std::endl;return 0;
}
3、案例输出
该示例源代码程序经过编译调试,在命令行输出信息如下:
(四)结语
本文重点介绍了C++共享类型的智能指针std::shared_ptrd的用法,并通过工控组态软件两种场景案例剖析了详细使用过程,读者可以在自己的开发工作实践中借鉴参考,欢迎提出宝贵意见。使用智能指针可以简化应用软件对内存对象生存期的管理,开发者真正理解和熟练使用后,可在某种程度上减少内存泄漏发生的情况。今天到此为止,欢迎各位提出宝贵意见和建议。