智能指针+拷贝构造+vector容器+多态引起的bug

今天在调试一段代码的时候,VC编译提示:

error C2280: “T485CommCtrlPara::T485CommCtrlPara(const T485CommCtrlPara &)”: 尝试引用已删除的函数

函数执行部分如下:

 看意思是这个pComm485Pro已经消亡了,后续push_back到vec485DevCommPara有问题,但智能指针已经move了,这样new出来资源的所有权应该已经转移了,为啥还会有问题呢?

找了下chatgpt和newbing查了下,他提供了一个馊主意:

 这个是把智能指针转成普通指针操作一把,后续还要delete,得不偿失,而且只是编译通过,问题依然存在。

本文就针对这个问题,详细分析下,先把有问题代码精简下,贴出来:

#include <iostream>
#include <string>
#include <vector>
#include <memory>typedef struct {std::string strPortName;unsigned int dwBaudRate;unsigned char bByteSize;unsigned char bStopBits;unsigned char bParity;
} TCommPara;class CComm
{
public:config(){;};
} class CMeterProto
{
;
}class cModbusProto:public CMeterProto
{
public:CModbusProto(const unsigned char bBAPn, const TCommPara tCommPara): m_bBAPn(bBAPn), m_tCommPara(tCommPara) {}
private:unsigned char m_bBAPn;TCommPara m_tCommPara;
}typedef struct {std::unique_ptr<CMeterProto> pComm485Pro{nullptr};CComm cMterCom;
}T485CommCtrlPara;int LoadBA485CommPara(TCommPara &tCommPara, T485CommCtrlPara &t485CommPara, vector<T485CommCtrlPara>& vec485DevCommPara)
{tCommPara.strPortName = "com1";tCommPara.dwBaudRate = 9600;tCommPara.bByteSize = 8;tCommPara.bStopBits = 1;t485CommPara.cMterCom.config();std::unique_ptr<CMeterProto> pComm485Pro(new CModbusProto(1, tCommPara));t485CommPara.pComm485Pro = std::move(pComm485Pro);vec485DevCommPara.push_back(t485CommPara);return 1;
}int main()
{TCommPara tCommPara;T485CommCtrlPara t485CommPara;vector<T485CommCtrlPara> vec485DevCommPara;int nBaNum = LoadBA485CommPara(tCommPara, t485CommPara, vec485DevCommPara);std::count<<nBaNum<<std::endl;
}

通过将如上代码交给chatgpt分析,开始他巴拉巴拉说没啥问题,后来我把VC的编译信息交给他

如上代码,在vs 2015上编译,提示:error C2280: “T485CommCtrlPara::T485CommCtrlPara(const T485CommCtrlPara &)”: 尝试引用已删除的函数

这个时候它开始分析代码了,经过几轮的修改,他终于还是发现了是push_back的问题。

主要原因就是push_back本质上是调用拷贝构造函数,将资源拷贝到vector的堆空间上,如果使用默认拷贝函数,就是浅拷贝,不会将pComm485Pro的资源拷贝过去,所以push_back拷贝的只是一个地址,实际资源并没有拷贝

原因知道后,修改就很简单了,实现拷贝构造函数即可,如上的

typedef struct {
    std::unique_ptr<CMeterProto> pComm485Pro{nullptr};
    CComm cMterCom;
}T485CommCtrlPara;

要定义下拷贝构造函数,按照chatgpt的实现,

T485CommCtrlPara(const T485CommCtrlPara& other)
        : pComm485Pro(other.pComm485Pro ? std::make_unique<CMeterProto>(*other.pComm485Pro) : nullptr),
          cMterCom(other.cMterCom)
    {
        // 在自定义拷贝构造函数中,正确拷贝智能指针成员
    }

编译不了,因为直接在struct中定义拷贝构造,vc编译还有问题,既然如此,修改成类方式好了:

  如上,编译后的时候提示了一个问题:

error C2512: “T485CommCtrlPara”: 没有合适的默认构造函数可用

这个就是常见的三法则了,如果手动定义了拷贝构造,编译器将不再自动生成默认构造,此处需要添加上:

 就可以正常运行了。

按照如上方式,修改实际工程中的代码,发现一个问题:

error C2259: “CMeterProto”: 不能实例化抽象类

这个就是CMeterProto中有一个纯虚函数:

virtual int GetData() = 0;

因为抽象类在new的时候无法指向具体的位置,导致实例化类失败,修改为普通虚函数就可以了:

virtual int GetData() {return 0};

如上,修改完成后就都编译,运行成功了。

将全部代码贴下来吧:

 总结:

1:使用智能指针可以通过std::move转移所有权。

2:使用vector变量push_back一个对象或变量的时候,本质上是执行拷贝构造。

3:如果对象中有申请资源时,如果new内存,句柄等,需要手动实现拷贝构造

4:抽象类不能new实例

5:碰到问题的时候,一心指望chatgpt是不够的,自己对于基本原理还是要门清,否则带去沟里也不知道

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/4344.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】MHA高可用配置及故障切换

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 MHA高可用配置及故障切换 MHA高可用配置及故障切换MHA概述MHA 的组成MHA 的特点 搭建 MySQL MHA1&#xff0e;Master、Slave1、Slave2 节点上安装 mysql5.72&#xff0e;修改…

Web项目思路

登录注册 账号密码登录&#xff0c;注册时生成唯一id。 找回密码 通过验证邮箱来修改现在的密码。 主页 显示最受欢迎的博主&#xff0c;点赞次数最多的博客&#xff0c;根据用户收藏夹中的偏好推荐博客等 遍历数据库&#xff0c;找到粉丝数最多的博主和点赞次数最多的博客…

配置本地的application-dev.yml读取nacos上面的配置

我想配置本地的application-dev.yml读取nacos上面的配置&#xff0c;我应该在配置文件里怎么写&#xff1f;并且在nacos里怎么建立 在本地的application-dev.yml文件中&#xff0c;您可以使用Spring Cloud Nacos来读取Nacos上的配置。以下是在配置文件和Nacos中设置的步骤&…

火爆全网,Jenkins创建容器-Python自动化测试环境搭建(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 下载 Jenkins 镜像…

【Spring】Spring框架介绍,功能模块,容器知识和有关Spring的生态圈的详细讲解

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

Java垃圾搜集算法和垃圾回收算法

垃圾回收主要针对的是JVM的堆内存&#xff0c;分为新生代和老年代。 按照以前的叫法&#xff0c;还有一个永久代&#xff0c;它在方法区里保存了class信息、静态变量、常量池等。 从jdk-1.8开始&#xff0c;方法区的实现发生了一些变化&#xff0c;取消了永久代的概念&#xff…

RabbitMQ管理界面介绍

1.管理界面概览 connections&#xff1a; 无论生产者还是消费者&#xff0c;都需要与RabbitMQ建立连接后才可以完成消息的生产和消费&#xff0c;在这里可以查看连接情况 channels&#xff1a; 通道&#xff0c;建立连接后&#xff0c;会形成通道&#xff0c;消息的投递获取依…

【网络编程】自定义协议+Json序列化与反序列化

文章目录 一、序列化与反序列化概念二、自定义协议设计网络计算机2.1 服务端2.1.1 服务端业务处理流程2.1.2 TCP的发送与接收缓冲区2.1.3 保证读取完整报文2.1.4 自定义协议——序列化与反序列化2.1.4.1 请求2.4.1.2 响应 2.1.5 计算流程2.1.6 在有效载荷前添加长度报头2.1.7 发…

威胁建模之绘制数据流图

0x00 前言 1、什么是威胁建模&#xff1a; 以结构化的方式思考、记录并讨论系统存在的安全威胁&#xff0c;并针对这些威胁制定相应的消减措施。 2、为什么要威胁建模&#xff1a; &#xff08;1&#xff09;在设计阶段开展威胁建模&#xff0c;一方面可以更全面的发现系统存…

处理glibc堆栈缓冲区溢出漏洞(CVE-2018-11236)

GNU C Library&#xff08;又名glibc&#xff0c;libc6&#xff09;是一种按照LGPL许可协议发布的开源免费的C语言编译程序。 GNU C库&#xff08;aka glibc或libc6&#xff09;中的stdlib/canonicalize.c处理非常长的路径名参数到realpath函数时&#xff0c;可能会遇到32位体系…

Electron中启动node服务

记一次遇到的问题&#xff0c;我们知道Electron 中主进程是在node环境中&#xff0c;所以打算在node环境中再启动一个node服务。但是直接使用exec命令启动就会卡主。对应的代码如下 // 启动Node server const startServer async () > {try {console.log(开始启动node serv…

Docker学习笔记11

Docker容器镜像&#xff1a; 1&#xff09;docker client 向docker daemon发起创建容器的请求&#xff1b; 2&#xff09;docker daemon查找本地有客户端需要的镜像&#xff1b; 3&#xff09;如无&#xff0c;docker daemon则到容器的镜像仓库中下载客户端需要的镜像&#…