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对象管理和内存管理的理解。希望这篇文章能对遇到类似问题的开发者有所帮助。

相关推荐
邪修king31 分钟前
UE5 进阶篇第一弹:中期架构升级 —— 组件化开发与 Gameplay 框架实战
c++·游戏·架构·ue5
zhangfeng11332 小时前
openclaw skills 小龙虾技能 通讯仿真 matlab skill Simulink Agentic Toolkit,通过kimi找到,mcp通讯
开发语言·matlab·openclaw·通讯仿真
Javatutouhouduan8 小时前
2026Java面试的正确打开方式!
java·高并发·java面试·java面试题·后端开发·java编程·java八股文
chao1898448 小时前
基于 SPEA2 的多目标优化算法 MATLAB 实现
开发语言·算法·matlab
JAVA面经实录9178 小时前
Java初级最终完整版学习路线图
java·spring·eclipse·maven
赏金术士8 小时前
Kotlin 习题集 · 高级篇
android·开发语言·kotlin
Cat_Rocky9 小时前
k8s-持久化存储,粗浅学习
java·学习·kubernetes
楼兰公子9 小时前
buildroot 在编译rust时裁剪平台类型数量的方法
开发语言·后端·rust
知识领航员10 小时前
蘑兔AI音乐深度实测:功能拆解、实测表现与适用场景
java·c语言·c++·人工智能·python·算法·github
吴声子夜歌10 小时前
Go——并发编程
开发语言·后端·golang