今天在调试一段代码的时候,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是不够的,自己对于基本原理还是要门清,否则带去沟里也不知道