As mentioned yesterday: When a vector variable pushes_back an object or variable, it essentially performs copy construction, but I want to use move construction instead of copy construction. This article will modify the debugging process and analyze in detail how to implement move construction.
Yesterday’s code is as follows: (If anyone wants to test it, you can copy it directly)
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <utility>
typedef struct {
std::string strPortName;
unsigned int dwBaudRate;
unsigned char bByteSize;
unsigned char bStopBits;
unsigned char bParity;
} TCommPara;
class CComm {
public:
void config() {
// 配置串口
}
};
class CMeterProto {
public:
virtual bool Init(unsigned char bProp, unsigned char bPn, CComm *pComm) { return false; };
virtual bool Run() { return false; };
unsigned char m_bPn; //当前操作的测量点
unsigned char m_bProp; //设备类型
TCommPara m_tCommPara; //通讯参数
protected:
virtual int GetData() { return -1; };
virtual int SetData() { return -1; };
};
class CModbusProto : public CMeterProto {
public:
CModbusProto(const unsigned char bBAPn, const TCommPara tCommPara)
: m_bBAPn(bBAPn), m_tCommPara(tCommPara) {
// 构造函数实现
}
protected:
virtual int GetData() override;
private:
unsigned char m_bBAPn;
TCommPara m_tCommPara;
};
int CModbusProto::GetData()
{
return 0;
}
class T485CommCtrlPara
{
public:
T485CommCtrlPara() = default;
T485CommCtrlPara(const T485CommCtrlPara& other);
virtual ~T485CommCtrlPara() = default;
std::unique_ptr<CMeterProto> pComm485Pro;
CComm cMterCom;
};
T485CommCtrlPara::T485CommCtrlPara(const T485CommCtrlPara& other)
{
pComm485Pro = nullptr;
if (other.pComm485Pro)
{
// pComm485Pro = std::make_unique<CMeterProto>(*other.pComm485Pro);
pComm485Pro = std::unique_ptr<CMeterProto>(new CMeterProto(*other.pComm485Pro));
}
cMterCom = other.cMterCom;
std::cout << "copy-ctor" << std::endl;
}
int LoadBA485CommPara(TCommPara& tCommPara, T485CommCtrlPara& t485CommPara, std::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;
std::vector<T485CommCtrlPara> vec485DevCommPara;
int nBaNum = LoadBA485CommPara(tCommPara, t485CommPara, vec485DevCommPara);
vec485DevCommPara[0].pComm485Pro->Run();
std::cout << vec485DevCommPara.size() << std::endl;
std::getchar();
return 0;
}
In order to use move construction, first modify the class T485CommCtrlPara constructor as follows:
Note that when writing the move assignment at the beginning, T485CommCtrlPara is not added before operator= ::
The result always prompts: "error C2801: "operator =" must be a non-static member " .
( The previous code was written by chatgpt , and there was a grammatical error. After being prompted, chatgpt corrected the error )
After the above code modification is completed, it can be compiled and run, but push_back still prompts that the copy constructor is used (check debugging information). If you want to use move, you need to modify it to:
As above, it is obvious that move is called. Check the running information and it is true that move-ctor is called.
But this is just a sample program. In an actual program, it is impossible to define many variables in main for execution. Therefore, consider defining a function outside main to handle vec485DevCommPara, and add an interface as follows:
The code compiles ok and can run, but when I check the memory, I find an exception.
Memory exception! !
Cause Analysis:
The Load485CommPara function defines local variables,
T485CommCtrlPara t485CommPara;
TCommPara tCommPara;
After the function is executed, these two variables will be destroyed. The t485CommPara class is as follows:
class T485CommCtrlPara
{
public:
T485CommCtrlPara() = default;
T485CommCtrlPara(const T485CommCtrlPara& other);
virtual ~T485CommCtrlPara() = default;
std::unique_ptr<CMeterProto> pComm485Pro;
CComm cMterCom;
};
In this way, when vec485DevCommPara . emplace_back ( std :: move ( t485CommPara )) , pComm485Pro moves, and CComm cMterCom ; is just a member variable, so the move is not successful.
T485CommCtrlPara::T485CommCtrlPara(const T485CommCtrlPara& other)
{
pComm485Pro = nullptr;
if (other.pComm485Pro)
{
pComm485Pro = std::unique_ptr<CMeterProto>(new CMeterProto(*other.pComm485Pro));
}
cMterCom = other.cMterCom;
// cMterCom = std::move(other.cMterCom);
std::cout << "copy-ctor" << std::endl;
}
If both of the above are unsuccessful, the best way is to use CComm cMterCom which is also defined as a smart pointer, as follows:
class T485CommCtrlPara
{
public:
T485CommCtrlPara() = default;
T485CommCtrlPara(const T485CommCtrlPara& other);
T485CommCtrlPara(T485CommCtrlPara&& other) noexcept;
T485CommCtrlPara& operator=(T485CommCtrlPara&& other) noexcept;
virtual ~T485CommCtrlPara() = default;
std::unique_ptr<CMeterProto> pComm485Pro;
std::unique_ptr<CComm> pComm485;
};
T485CommCtrlPara::T485CommCtrlPara(const T485CommCtrlPara& other)
{
pComm485Pro = nullptr;
if (other.pComm485Pro)
{
pComm485Pro = std::unique_ptr<CMeterProto>(new CMeterProto(*other.pComm485Pro));
pComm485 = std::unique_ptr<CComm>(new CComm(*other.pComm485));
}
std::cout << "copy-ctor" << std::endl;
}
T485CommCtrlPara::T485CommCtrlPara(T485CommCtrlPara&& other) noexcept
{
pComm485Pro = std::move(other.pComm485Pro);
pComm485 = std::move(other.pComm485);
std::cout << "move-ctor" << std::endl;
}
After completion, the operation is basically normal:
As above, it can run normally, but there is still a problem, that is, the base class member m_tCOmmPara of pComm485Pro is still empty. This is because when new CModbusProto (1, tCommPara ) , the constructor of CModbusProto is called , but the base class CMeterProto does not have a constructor (or only has a default constructor, but no value assignment). Therefore, although the base class also defines m_tCommPara , But no value is actually assigned. How to be more perfect?
Add the base class structure and modify the structure of the inherited class as follows:
First modify the base class and add a constructor:
class CMeterProto {
public:
CMeterProto(const unsigned char& bPn, const TCommPara& tCommPara) :m_bPn(bPn), m_tCommPara(tCommPara) {}
virtual bool Init(unsigned char bProp, unsigned char bPn, CComm *pComm);
virtual int Run();
unsigned char m_bProp; //设备类型
protected:
virtual int GetData();
virtual int SetData() { return -1; };
unsigned char m_bPn; //当前操作的测量点
TCommPara m_tCommPara; //通讯参数
CComm *m_pComm;
};
class CModbusProto : public CMeterProto
{
public:
CModbusProto(const unsigned char bBAPn, const TCommPara tCommPara):
CMeterProto { bBAPn, tCommPara } {}
//unsigned char m_bBAPn; //当前操作的测量点
//TCommPara m_tCommPara; //通讯参数
protected:
virtual int GetData() override;
};
After the above modifications, compile and run, the data is correct, and the results are as follows:
As above, it runs normally. I will paste the complete code below.
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <utility>
typedef struct {
std::string strPortName;
unsigned int dwBaudRate;
unsigned char bByteSize;
unsigned char bStopBits;
unsigned char bParity;
} TCommPara;
class CComm {
public:
void config(const TCommPara&);
private:
TCommPara m_tCommPara;
};
void CComm::config(const TCommPara& tCommPara)
{
m_tCommPara = tCommPara;
}
class CMeterProto {
public:
CMeterProto(const unsigned char& bPn, const TCommPara& tCommPara) :m_bPn(bPn), m_tCommPara(tCommPara) {}
virtual bool Init(unsigned char bProp, unsigned char bPn, CComm *pComm);
virtual int Run();
protected:
virtual int GetData();
virtual int SetData() { return -1; };
unsigned char m_bPn; //当前操作的测量点
TCommPara m_tCommPara; //通讯参数
unsigned char m_bProp; //设备类型
CComm *m_pComm;
};
bool CMeterProto::Init(unsigned char bProp, unsigned char bPn, CComm *pComm)
{
m_bProp = bProp;
m_bPn = bPn;
m_pComm = pComm;
return true;
}
int CMeterProto::GetData()
{
std::cout << "CMeterProto-GetData" << std::endl;
return -1;
}
int CMeterProto::Run()
{
return GetData();
}
class CModbusProto : public CMeterProto
{
public:
CModbusProto(const unsigned char bBAPn, const TCommPara tCommPara):
CMeterProto { bBAPn, tCommPara } {}
//unsigned char m_bBAPn; //当前操作的测量点
//TCommPara m_tCommPara; //通讯参数
protected:
virtual int GetData() override;
};
int CModbusProto::GetData()
{
std::cout << "CModbusProto-GetData" << std::endl;
return 0;
}
class T485CommCtrlPara
{
public:
T485CommCtrlPara() = default;
T485CommCtrlPara(const T485CommCtrlPara& other);
T485CommCtrlPara(T485CommCtrlPara&& other) noexcept;
T485CommCtrlPara& operator=(T485CommCtrlPara&& other) noexcept;
virtual ~T485CommCtrlPara() = default;
std::unique_ptr<CMeterProto> pComm485Pro;
std::unique_ptr<CComm> pComm485;
};
T485CommCtrlPara::T485CommCtrlPara(const T485CommCtrlPara& other)
{
pComm485Pro = nullptr;
if (other.pComm485Pro && other.pComm485)
{
pComm485Pro = std::unique_ptr<CMeterProto>(new CMeterProto(*other.pComm485Pro));
pComm485 = std::unique_ptr<CComm>(new CComm(*other.pComm485));
}
std::cout << "copy-ctor" << std::endl;
}
T485CommCtrlPara::T485CommCtrlPara(T485CommCtrlPara&& other) noexcept
{
if (this != &other && other.pComm485Pro && other.pComm485)
{
pComm485Pro = std::move(other.pComm485Pro);
pComm485 = std::move(other.pComm485);
}
std::cout << "move-ctor" << std::endl;
}
T485CommCtrlPara& T485CommCtrlPara::operator=(T485CommCtrlPara&& other) noexcept
{
if (this != &other)
{
pComm485Pro = std::move(other.pComm485Pro);
pComm485 = std::move(other.pComm485);
}
std::cout << "move-operator-ctor" << std::endl;
return *this;
}
int LoadBA485CommPara(TCommPara& tCommPara, T485CommCtrlPara& t485CommPara, std::vector<T485CommCtrlPara>& vec485DevCommPara)
{
tCommPara.strPortName = "com1";
tCommPara.dwBaudRate = 9600;
tCommPara.bByteSize = 8;
tCommPara.bStopBits = 1;
std::unique_ptr<CMeterProto> pComm485Pro(new CModbusProto(1, tCommPara));
std::unique_ptr<CComm> pComm485(new CComm());
pComm485->config(tCommPara);
t485CommPara.pComm485Pro = std::move(pComm485Pro);
t485CommPara.pComm485 = std::move(pComm485);
t485CommPara.pComm485Pro->Init(1, 1, t485CommPara.pComm485.get());
vec485DevCommPara.emplace_back(std::move(t485CommPara));
return 1;
}
int Load485CommPara(std::vector<T485CommCtrlPara>& vec485DevCommPara)
{
T485CommCtrlPara t485CommPara;
TCommPara tCommPara;
return LoadBA485CommPara(tCommPara, t485CommPara, vec485DevCommPara);
}
int main()
{
std::vector<T485CommCtrlPara> vec485DevCommPara;
Load485CommPara(vec485DevCommPara);
vec485DevCommPara[0].pComm485Pro->Run();
std::cout << vec485DevCommPara.size() << std::endl;
std::getchar();
return 0;
}
Summarize:
1: Smart pointers facilitate memory management, but the complexity has increased a lot. The theory alone is not enough, and you need to practice and summarize frequently.
2: Chatgpt or newbing will help a lot, but you can’t rely too much on it. You need to ask the right questions, but having an expert by your side can really help a lot.
3: Containers and smart pointers are necessary basic operations of modern C++. Use them as soon as possible.