C++深度拷贝例子

伺服设备参数界面valueEdit冲突Bug修复

问题描述

在使用ECAT Master软件管理多个伺服设备时,发现一个严重的bug:当读取一个伺服设备的参数后,其他伺服设备的参数界面也会显示相同的参数值,导致参数显示混乱。

具体现象

  • 有3个伺服设备(伺服1、伺服2、伺服3)
  • 读取伺服1的参数后,伺服2和伺服3的参数界面也会显示与伺服1相同的参数值
  • 每个伺服设备的valueEdit控件显示的值相互影响,无法正确显示各自的参数

根本原因分析

经过代码分析,发现问题的根本原因是多个ServoDrive对象共享同一个XmlFile和ObjectDic对象

详细分析

  1. XmlFile对象共享 :在ServoDrive::setXmlFileServoDrive::setDefaultXmlFile方法中,直接将传入的XmlFile指针赋值给当前ServoDrive对象,没有创建深拷贝
  2. ObjectDic对象共享:每个XmlFile包含一个ObjectDic列表,用于存储参数信息。由于XmlFile被共享,所有ServoDrive对象实际上使用的是同一个ObjectDic列表
  3. 信号槽连接:每个valueEdit控件通过信号槽连接到ObjectDic的readFinish信号,当一个ObjectDic的值更新时,所有连接到它的valueEdit都会更新

代码问题点

原代码(问题代码)
cpp 复制代码
// 设置XML文件
void ServoDrive::setXmlFile(XmlFile* xmlFile)
{
  if(xmlFile->vendorID == this->Vendor_ID && xmlFile->productCode == this->ProductCode){
    this->xmlFile = xmlFile;  // 直接赋值,共享同一个XmlFile对象
  }
}

// 设置默认XML文件
void ServoDrive::setDefaultXmlFile(XmlFile* xmlFile)
{
  this->xmlFile = xmlFile;  // 直接赋值,共享同一个XmlFile对象
}

解决方案

核心思路

为每个ServoDrive对象创建独立的XmlFile和ObjectDic对象,确保参数值的存储和更新互不影响。

具体实现

  1. 修改ServoDrive::setXmlFile方法

    • 创建XmlFile的深拷贝,包括所有属性
    • 深拷贝ObjectDic列表,为每个ObjectDic创建独立的拷贝
    • 构建快速查找表,确保查找功能正常
  2. 修改ServoDrive::setDefaultXmlFile方法

    • 同样实现深拷贝逻辑
  3. 修改ServoDrive::~ServoDrive析构函数

    • 添加对xmlFile的释放逻辑,避免内存泄漏

修复后代码

修改ServoDrive::setXmlFile方法
cpp 复制代码
/**
 * @brief 设置XML文件
 * @param xmlFile XML文件对象
 * @details 当XML文件的厂商ID和产品代码与伺服匹配时,创建XML文件的深拷贝并设置为当前XML文件
 */
void ServoDrive::setXmlFile(XmlFile* xmlFile)
{
  if(xmlFile->vendorID == this->Vendor_ID && xmlFile->productCode == this->ProductCode){
    // 创建XmlFile的深拷贝,确保每个ServoDrive有自己独立的ObjectDic对象
    XmlFile* copyXmlFile = new XmlFile(this);
    copyXmlFile->filename = xmlFile->filename;
    copyXmlFile->vendorID = xmlFile->vendorID;
    copyXmlFile->productCode = xmlFile->productCode;
    copyXmlFile->revisionNo = xmlFile->revisionNo;
    copyXmlFile->typeName = xmlFile->typeName;
    copyXmlFile->Icon = xmlFile->Icon;
    copyXmlFile->bOdicValid = xmlFile->bOdicValid;
    
    // 深拷贝ObjectDic列表
    copyXmlFile->oList = new QList<ObjectDic*>();
    copyXmlFile->oMap = new QMap<quint32, ObjectDic*>();
    
    if(xmlFile->oList) {
        for(ObjectDic* obj : *xmlFile->oList) {
            ObjectDic* copyObj = new ObjectDic(copyXmlFile);
            copyObj->index = obj->index;
            copyObj->subindex = obj->subindex;
            copyObj->name = obj->name;
            copyObj->type = obj->type;
            copyObj->bitSize = obj->bitSize;
            copyObj->min = obj->min;
            copyObj->max = obj->max;
            copyObj->defVal = obj->defVal;
            copyObj->curVal = obj->curVal;
            copyObj->access = obj->access;
            copyObj->Category = obj->Category;
            copyObj->checked = obj->checked;
            
            copyXmlFile->oList->append(copyObj);
        }
        
        // 构建快速查找表
        copyXmlFile->buildObjectMap();
    }
    
    this->xmlFile = copyXmlFile;
  }
}
修改ServoDrive::setDefaultXmlFile方法
cpp 复制代码
/**
 * @brief 设置默认XML文件
 * @param xmlFile XML文件对象
 * @details 直接设置为当前XML文件,不进行厂商ID和产品代码的匹配,创建XML文件的深拷贝
 */
void ServoDrive::setDefaultXmlFile(XmlFile* xmlFile)
{
  // 创建XmlFile的深拷贝,确保每个ServoDrive有自己独立的ObjectDic对象
  XmlFile* copyXmlFile = new XmlFile(this);
  copyXmlFile->filename = xmlFile->filename;
  copyXmlFile->vendorID = xmlFile->vendorID;
  copyXmlFile->productCode = xmlFile->productCode;
  copyXmlFile->revisionNo = xmlFile->revisionNo;
  copyXmlFile->typeName = xmlFile->typeName;
  copyXmlFile->Icon = xmlFile->Icon;
  copyXmlFile->bOdicValid = xmlFile->bOdicValid;
  
  // 深拷贝ObjectDic列表
  copyXmlFile->oList = new QList<ObjectDic*>();
  copyXmlFile->oMap = new QMap<quint32, ObjectDic*>();
  
  if(xmlFile->oList) {
      for(ObjectDic* obj : *xmlFile->oList) {
          ObjectDic* copyObj = new ObjectDic(copyXmlFile);
          copyObj->index = obj->index;
          copyObj->subindex = obj->subindex;
          copyObj->name = obj->name;
          copyObj->type = obj->type;
          copyObj->bitSize = obj->bitSize;
          copyObj->min = obj->min;
          copyObj->max = obj->max;
          copyObj->defVal = obj->defVal;
          copyObj->curVal = obj->curVal;
          copyObj->access = obj->access;
          copyObj->Category = obj->Category;
          copyObj->checked = obj->checked;
          
          copyXmlFile->oList->append(copyObj);
      }
      
      // 构建快速查找表
      copyXmlFile->buildObjectMap();
  }
  
  this->xmlFile = copyXmlFile;
}
修改ServoDrive::~ServoDrive析构函数
cpp 复制代码
/**
 * @brief 析构函数
 * @details 释放资源
 */
ServoDrive::~ServoDrive()
{
  // 释放资源
  // 释放自己创建的XmlFile对象
  if (xmlFile) {
      delete xmlFile;
      xmlFile = nullptr;
  }
}

改动前后对比

项目 改动前 改动后
XmlFile对象 多个ServoDrive共享同一个XmlFile对象 每个ServoDrive有自己独立的XmlFile对象
ObjectDic对象 多个ServoDrive共享同一个ObjectDic列表 每个ServoDrive有自己独立的ObjectDic列表
参数值存储 所有ServoDrive的参数值存储在同一个ObjectDic中 每个ServoDrive的参数值存储在自己的ObjectDic中
内存管理 XmlFile由XmlUtil管理,ServoDrive不释放 ServoDrive在析构时释放自己创建的XmlFile
行为表现 读取一个伺服的参数会影响其他伺服的显示 每个伺服的参数显示独立,互不影响

技术要点

  1. 深拷贝实现

    • 不仅拷贝XmlFile的基本属性,还要深拷贝其中的ObjectDic列表
    • 为每个ObjectDic创建独立的拷贝,确保参数值的存储空间独立
  2. 快速查找表

    • 为拷贝后的XmlFile重新构建oMap,确保通过index和subindex查找ObjectDic的功能正常
  3. 内存管理

    • 在ServoDrive析构时释放自己创建的XmlFile对象,避免内存泄漏
    • 确保XmlFile的生命周期与ServoDrive一致
  4. 信号槽机制

    • 每个valueEdit控件连接到对应的ObjectDic的信号
    • 由于ObjectDic独立,信号只会影响对应的valueEdit控件

测试验证

测试步骤

  1. 启动ECAT Master软件
  2. 连接3个伺服设备
  3. 读取伺服1的参数
  4. 切换到伺服2的参数页面,验证显示的是伺服2的参数
  5. 读取伺服2的参数
  6. 切换回伺服1的参数页面,验证显示的仍是伺服1的参数
  7. 重复上述步骤,确保参数显示正确且互不影响

预期结果

  • 每个伺服设备的参数页面显示各自的参数值
  • 读取一个伺服的参数不会影响其他伺服的参数显示
  • 软件运行稳定,无内存泄漏

结论

通过为每个ServoDrive对象创建独立的XmlFile和ObjectDic对象,成功解决了伺服设备参数界面valueEdit冲突的bug。这一修复确保了每个伺服设备的参数值存储和显示独立,提高了软件的可靠性和用户体验。

技术启示

  • 在设计多对象共享数据时,应考虑是否需要深拷贝
  • 对于包含复杂数据结构的对象,深拷贝是确保数据独立性的重要手段
  • 内存管理是C++编程中的关键环节,需要确保对象的正确创建和释放
  • 信号槽机制在Qt中是实现组件间通信的强大工具,但需要注意信号的作用范围

后续建议

  1. 代码优化:可以考虑实现XmlFile的拷贝构造函数,使深拷贝逻辑更加清晰
  2. 单元测试:为ServoDrive类添加单元测试,确保深拷贝功能正常
  3. 文档更新:更新相关文档,说明ServoDrive对象的数据独立性
  4. 性能优化:对于大量参数的情况,可以考虑延迟加载或其他性能优化策略

通过这次bug修复,不仅解决了实际问题,也加深了对Qt对象管理和内存管理的理解。希望这篇文章能对遇到类似问题的开发者有所帮助。

相关推荐
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【4】ReAct 范式与 ReactAgent 核心设计
java·人工智能·spring
Knight_AL2 小时前
Nacos 启动问题 Failed to create database ’D:\nacos\nacos\data\derby-data’
开发语言·数据库·python
「QT(C++)开发工程师」2 小时前
C++11三大核心特性深度解析:类型特征、时间库与原子操作
java·c++·算法
乐分启航2 小时前
SliMamba:十余K参数量刷新SOTA!高光谱分类的“降维打击“来了
java·人工智能·深度学习·算法·机器学习·分类·数据挖掘
leiming62 小时前
CAN 通信协议学习讲义(带图文 + C 语言代码)
c语言·开发语言·学习
xht08323 小时前
PHP vs C语言:核心差异全解析
c语言·开发语言·php
yoothey3 小时前
Java字节流与字符流核心笔记(问答+考点复盘)
java·开发语言·笔记
查古穆3 小时前
python进阶-Pydantic模型
开发语言·python
沐知全栈开发3 小时前
Bootstrap4 导航栏
开发语言