C++ 左值 右值 移動構造函數 移動賦值運算符

一、左值、右值、左值引用&、右值引用&&

main.cpp

#include <iostream>
using namespace std;

int main()
{
	string str = "I love china";
	const char *p = str.c_str();
	cout << (void*)p << endl;
	string &str1 = str;//str爲左值, str1爲左值引用
	string &&str2 = "I love china";//"I love china"爲右值,str2爲右值引用
	string str3 = std::move(str);//str左值轉右值,只有轉右值纔會調用移動構造函數,否則調用的是拷貝構造函數。
	//調用了string的移動構造函數,原str爲空,值轉移到str3了
	const char *p1 = str3.c_str();
	cout << (void*)p1 << endl;//p和p1不一致,說明並不是名副其實的移動

	string str4 = "I love china";
	string && str5 = std::move(str4);//不會調用移動構造函數,只是個左值轉右值
	str5 = "abc";//str5和str4同步修改
	str4 = "edf";//str5和str4同步修改

	int i = 0;
	int& i1 = i;//i是左值,i1是左值引用
	int&& i2 = 1;//1是右值,i2是右值引用
	int && i3 = std::move(i);//左值轉右值,賦值給右值引用*/
}

左值:可以被賦值的,用左值引用&

右值:不能被賦值的,臨時對象,用右值引用&&

 

二、移動構造函數、移動賦值運算符

#include <iostream>
using namespace std;
class B
{
public:
	B(): m_bm(100)
	{

	}

	B(const B& tmp): m_bm(tmp.m_bm)
	{

	}

	virtual ~B()
	{

	}
	int m_bm;
};

class A
{
public:
	A() :m_pb(new B()), kk(34)
	{
		cout << "類A的構造函數執行了" << endl;
	}

	A(const A& tmpa):m_pb(new B(*(tmpa.m_pb))),kk(tmpa.kk)//m_pb指向新的B對象
	{
		cout << "類A的拷貝構造函數執行了" << endl;
	}

	A(A&& tmpa):m_pb(tmpa.m_pb), kk(tmpa.kk)//m_pb指向舊的B對象
	{
		tmpa.m_pb = nullptr;
		cout << "類A的移動構造執行了" << endl;
	}

	A &operator=(A && src)
	{
		if (this == &src)
			return *this;
		delete m_pb;
		m_pb = src.m_pb;
		kk = src.kk;
		src.m_pb = nullptr;
		cout << "類A的移動賦值運算符執行了" << endl;
		return *this;
	}

	virtual ~A()
	{
		delete m_pb;
		cout << "類A的析構函數執行了" << endl;
	}
private:
	B* m_pb;
	int kk;
};

static A getA()
{
	A a;//臨時對象爲右值
	return a;
}

int main()
{
    A a = getA();
}

輸出:

類A的構造函數執行了
類A的移動構造執行了(臨時對象是右值,如果沒有移動構造函數,那隻能拷貝構造函數了)
類A的析構函數執行了
類A的析構函數執行了

int main()
{
    A a;
    A a1(std::move(a));//左值轉右值後調用移動構造函數
}

輸出:

類A的構造函數執行了
類A的移動構造執行了
類A的析構函數執行了
類A的析構函數執行了

a和a1的成員變量m_pb和kk還是重新分配的,只不過m_pb指向的內存直接用原來的了,原來m_pb爲NULL。

int main()
{
    A a;   
    A a2;
    a2 = std::move(a);//移動賦值運算符
}

輸出:

類A的構造函數執行了
類A的構造函數執行了
類A的移動賦值運算符執行了
類A的析構函數執行了
類A的析構函數執行了

 

總結:

有自己的拷貝構造函數、拷貝賦值運算符、或者析構函數,編譯器就不會爲我們生成默認的移動構造函數、移動賦值運算符
如果不是上面的情況,滿足如下條件,編譯器會爲我們生成默認的移動構造函數、移動賦值運算符
1、成員變量可移動(右值)

2、成員變量爲對象,對象可移動(有移動構造函數或者本身可移動)

爲什麼要引入右值和右值引用的概念呢?就是爲了編寫移動構造函數,編寫移動構造函數就是爲了提高效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章