vetcor uses move construction instead of copy construction to implement push_back

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.

Guess you like

Origin blog.csdn.net/weixin_45119096/article/details/131443910