智能指针+拷贝构造+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是不够的,自己对于基本原理还是要门清,否则带去沟里也不知道

猜你喜欢

转载自blog.csdn.net/weixin_45119096/article/details/131425220