C++移動語義與右值引用完美展示與原理介紹 更新於2018-05-01

CalmReason發表於2016-08-23

C++移動語義

移動語義顧名思義是記憶體資料的移動,實際就是記憶體資料所有權的轉移。

目的:

1 函式可以直接返回值型別的物件而不發生物件拷貝

2 除了函式返回值,自定義型別的普通賦值也可以走移動語義,目的相同:減少記憶體開闢和釋放(RapidJson庫就是這種情況)

例如:vector<int>  fun(void);

由於vector的移動建構函式會將fun內部返回的vector所擁有的記憶體直接通過swap的方式跟返回值物件交換。所以沒有發生重新構造一個vector<int>並重新開闢記憶體再賦值這個過程。從而移動就等於偷樑換柱了一樣,當然,要求移動之後,源物件要能夠正常析構,且成員資料是未知的(已經被轉移走了,只剩下空指標了)

直接上程式碼:

以自定義類MyString來演示建立多個物件卻不用開闢新記憶體,也不用拷貝記憶體:

下面的getString函式內部,建立了三個物件,但是它們的資源卻在像接力棒一樣的傳給下一個。從而不用開闢新記憶體,也不用拷貝內容。

最後一個物件用於真正的列印工作。

原理:

T&& std::move(const T& t) 

  1. std::move用於返回物件的右值引用
  2. 而MyString的建構函式接受一個右值引用物件
  3. MyString的建構函式在得到右值引用物件後交換資源,從而實現了偷樑換柱。避免了記憶體的重新開闢、拷貝、釋放的過程。

總結:

從本例程式碼來看,右值引用相當於在引數傳遞的方式上增加了一種傳參的形式。

原有的傳參形式:

  • 傳地址:T*  C語言用法
  • 傳引用:T&  常用於修改物件
  • 傳應用:const T&  常用於只讀物件
  • 傳值: T  常用於小型物件非頻繁傳值
  • 傳右值引用: T&& 常用於臨時物件的資源交換,複雜物件避免拷貝

//// move example
#include <utility>      // std::move
#include <iostream>     // std::cout
#include <vector>       // std::vector
#include <string>       // std::string
using namespace std;

class MyString
{
	friend ostream& operator<<(ostream& os, const MyString& str)
	{
		return	os<<str.m_pData;
	}
public:
	MyString(void)
		:m_pData(nullptr),m_length(0),m_id(++s_i)
	{
		cout<<"MyString("<<m_id<<")"<<endl;
	}
	~MyString()
	{
		cout<<"~MyString("<<m_id<<")"<<endl;
		Clear();
	}
	MyString(const char* _pData, int _length)
		:m_pData(nullptr),m_length(0),m_id(++s_i)
	{
		cout<<"MyString(const char*,int,"<<m_id<<")"<<endl;
		Copy(_pData, _length);
	}
	MyString(const MyString& _strFrom)
		:m_pData(nullptr),m_length(0),m_id(++s_i)
	{
		cout<<"MyString("<<m_id<<", const MyString& "<<_strFrom.m_id<<" )"<<endl;
		if (_strFrom.m_pData == m_pData)
		{
			//do nothing
		}
		else
		{
			Copy(_strFrom.m_pData, _strFrom.m_length);
		}
	}
	//使用右值引用來建立物件時,使用資源交換來避免記憶體拷貝和重新建立
	MyString(MyString&& _strFrom)
		:m_pData(nullptr),m_length(0),m_id(++s_i)
	{
		cout<<"MyString("<<m_id<<", MyString&& "<<_strFrom.m_id<<" )"<<endl;
		if (_strFrom.m_pData == m_pData)
		{
			//do nothing
		}
		else
		{
			swap(m_pData,_strFrom.m_pData);
			swap(m_length,_strFrom.m_length);
		}
	}


protected:
	void Clear(void)
	{
		if (nullptr != m_pData)
		{
			delete[] m_pData;
			m_pData = nullptr;
			m_length = 0;
		}
	}
	void Copy(const char* _pData, int _length)
	{
		Clear();
		m_pData = new char[_length];
		for (size_t i = 0; i< _length; ++i)
		{
			m_pData[i] = _pData[i];
		}

		m_length = _length;
	}
private:
	char* m_pData;
	int m_length;

	int m_id;
	static int s_i;
};

int MyString::s_i = 0;

MyString getMyString(void)
{
	//下面的程式碼建立了三個MyString物件,只發生了1次開闢記憶體,0次記憶體拷貝
	MyString s("12345",6);//id == 1
	MyString s1(move(s));//id == 2
	return move(s1);//id == 3 這個被函式返回
	//~MyString(2)
	//~MyString(1)
}

int main () {
	
	cout<<getMyString()<<endl;//列印的是函式返回的id == 3的那個物件

	return 0;
}





相關文章