C++11移動語義與右值引用、移動構造函數與移動賦值運算符
文章目錄
C++11新特性移動語義,編譯器RVO技術,右值引用
都是爲了解決臨時變量的問題,讓系統的開銷變小。
一.前言
C++11爲什麼要引入右值引用?
總的來說,是爲了減小臨時變量多次申請空間的開銷。
引入了右值引用之後編譯器就能區分左值和右值(也就是可以區分哪些是臨時變量),也就可以調用移動構造函數與移動賦值運算符,對右值進行移動而不是拷貝,這樣減小了拷貝帶來的開銷。
總結:
1.右值引用只能關聯右值
3.左值引用只能關聯左值
2.常量左值引用均可關聯
二.移動語義
1.左值與右值
void test0()
{
int x = 10;
/**********右值引用***************/
int &&a = 5; //右值引用 右值
cout << "a = " << a << endl;
//int &&a2 = x; //右值引用 左值 ERROR!一個右值引用不能關聯左值
/**********左值引用***************/
const int &b = 5; //常量左值引用 右值
cout << "b = " << b << endl;
const int &b2 = x; //常量左值引用 左值
cout << "b2 = " << b2 << endl;
int &c = a; //左值引用 左值
cout << "c = " << c << endl;
//int &c2 = 1;//左值引用 右值 ERROR!
}
2.std::move 移動語義
move的功能是將一個左值變爲右值引用,同時將元素進行轉移(注意不是拷貝!)
void test1()
{
int a = 5;
int &&b = move(a);
cout << a << endl;
cout << b << endl;
string s = "HelloWorld";
printf("%p\n", &s);
vector<string> vec;
// vec.push_back(s);//copy
vec.push_back(std::move(s)); //move, 這裏s指向空間的內容已經全部被移動走了,所以爲空
printf("%p\n", &s);
cout << s << endl;
}
3.移動構造函數 移動賦值運算符
移動構造函數,傳入的String類型被系統判定爲右值,所以調用移動構造函數,這個步驟相比拷貝構造函數,省卻了在構造函數中申請空間的開銷,極大的節省了時間。
步驟:
1.將this指針中的_pstr指向右值的_pstr;
2.將右值的_pstr指向爲空;
3.右值析構,由於右值的_pstr已經指向爲空了,所以this指向的_pstr指向的空間並不會被釋放。
注意:
將右值的_pstr指向爲NULL非常重要,不然會出錯!
因爲右值馬上就會進行析構,如果不指向爲空的話就會釋放本來應該保留的this->_pstr指向的空間!
同樣,移動賦值運算符也是相同的道理。
下面的例子採用的是String類的編寫,其中採用了移動構造函數與移動賦值運算符,如果想了解完整的String類編寫,可以參考另外一篇博文:
class String
{
public:
String();
String(const char *);
String(const String &);
String(String &&lhs); //移動構造函數
~String();
String &operator=(const String &);
String &operator=(const char *);
String &operator=(String &&lhs); //移動賦值運算符
friend std::ostream &operator<<(std::ostream &os, const String &s);
friend std::istream &operator>>(std::istream &is, String &s);
private:
char *_pstr;
};
String::String()
: _pstr(new char('\0'))
{
cout << "String()" << endl;
}
String::String(const char *pstr)
: _pstr(new char[strlen(pstr) + 1])
{
cout << "String(const char*)" << endl;
memcpy(_pstr, pstr, strlen(pstr) + 1);
}
String::String(const String &lhs)
: _pstr(new char[strlen(lhs._pstr) + 1])
{
cout << "String(const String&)" << endl;
strcpy(_pstr, lhs._pstr);
}
/*
*/
String::String(String &&lhs)
{
cout << "String(String&&)" << endl;
_pstr = lhs._pstr;
lhs._pstr = NULL;
}
String::~String()
{
cout << "~String()" << endl;
if (_pstr)
{
delete[] _pstr;
_pstr = NULL;
}
}
String &String::operator=(const String &lhs)
{
delete[] _pstr;
_pstr = new char[strlen(lhs._pstr) + 1];
memcpy(_pstr, lhs._pstr, strlen(lhs._pstr) + 1);
return *this;
}
String &String::operator=(const char *s)
{
delete[] _pstr;
_pstr = new char[strlen(s) + 1];
memcpy(_pstr, s, strlen(s) + 1);
return *this;
}
//移動賦值運算符
String &String::operator=(String &&lhs)
{
if (this != &lhs)
{
cout << "String& operator=(String&&)" << endl;
_pstr = lhs._pstr;
lhs._pstr = NULL;
}
return *this;
}
ostream &operator<<(ostream &os, const String &s)
{
if (s._pstr)
{
os << s._pstr;
}
return os;
}
//測試移動構造函數
void test2(){
vector<String> vec;
vec.push_back(String("s1"));
cout << vec[0] << endl;
}
//測試移動賦值運算符
void test3(){
String s1("s1");
String s2("s2");
s1 = std::move(s2);
cout << s1 << endl;
}
三.RVO(Return Value optimization)返回值優化
我們在編寫程序的過程中可能會構造很多臨時變量並返回,如:
Data func(){
Data temp(1);
return Data;
}
如果按正常步驟來的話應該是:調用構造函數,再調用複製構造函數,再調用析構函數;
這很明顯很費時,所以我們應該使用RVO技術,也就是改變一種寫法:
Data RVO() {
return Data(1);
}
按照RVO的方法來,只需要調用一次構造函數就可以了。
下面是實現RVO優化技術的代碼:
class Data
{
public:
explicit Data(int val = 0) : _val(val) { cout << "Data(int)" << endl; }
~Data() { cout << "~Data()" << endl; }
//複製構造函數
Data(const Data &lhs)
{
_val = lhs._val;
cout << "Data(const Data&)" << endl;
}
//重載=運算符
Data& operator=(const Data& lhs)
{
_val = lhs._val;
cout << "Data& operator=(const Data&)" << endl;
}
private:
int _val;
};
//不使用RVO技術優化 分別調用構造函數,複製構造函數,析構函數
Data func() {
Data temp(1);
return temp;
}
//使用RVO技術優化 直接調用構造函數
Data RVO() {
return Data(1);
}
int main()
{
Data d = func();
return 0;
}
注意:關於函數func()在某些編譯器(如g++)會直接使用RVO技術導致只會調用一次構造函數,之後我改用VS2017後,編譯器就不會再自動使用RVO技術了