C++11特性——右值引用與移動構造函數

面試時遇到的問題,之前的瞭解是淺嘗輒止,但合格的程序媛應該不能止於此。

左值引用和右值引用

先理解兩個概念:左值和右值。早期C語言給出的定義是:左值是一個表達式,可以出現在=的左邊或右邊;但右值只能出現在右邊。 這個定義太模糊了。到了C++中,可以這麼理解:對於一個表達式,凡是對其取地址(&)操作可以成功的都是左值,否則就是右值。 好了,下面我們看幾個例子來加深下理解:

int x = 4;
int* p = &x;          //ok, x 是左值

int& fun();
fun() = 42; 	     // ok, fun() 是左值
int* p1 = &fun();    // ok, fun() 是左值

class A{};
A *a = &A();         //錯誤,A()是臨時變量,是右值

現在來看左值引用和右值引用的概念,在C++11之前,不存在右值引用,因此將左值引用直接稱爲“引用”。也就是說,我們曾經一直用的int& x = y事實上是對於int類型左值的引用。右值引用 是 C++ 11中引入的新特性 ,可以理解爲對右值的引用,我們知道在C++中右值不能被修改。但右值引用的出現,打破了這個限制,它允許我們獲取右值的引用,並修改之。

右值引用的形式爲:類型 && a= 被引用的對象 ,此外需要注意的是右值引用只能綁定到右值

int && x = 3;        //正確,3是右值

int x = 3;
int && y = x;        //錯誤,x是左值

引用作爲函數參數

C++中的(左值)引用可以用作函數的參數,並且也建議儘可能用引用作爲函數的參數,主要原因是傳引用比傳值效率更高。其實不光左值引用可以作爲函數參數,在C++中,右值引用也能作爲函數參數,下面看兩個函數:

void fun1(int & i)    //函數1 ,左值引用作參數
{ 
	cout << "int & " << endl; 
}
void fun2(int && i)  //函數2, 右值引用作參數
{ 
	cout << "int && " << endl; 
}

// 對於以下語句的調用及輸出如下:
int i = 12;
fun1(i);       //輸出 int &
fun2(22);      //輸出 int &&

拷貝構造函數的侷限性

考慮一個例子,我們定義了一個工廠函數獲得Test對象,然後在main()函數中創建了一個Test對象 t ,然後將調用工廠函數獲得Test對象初始化 t ,運行程序,看看發生了什麼。
在這裏插入圖片描述在這裏插入圖片描述
從運行結果來看,這個示例中調用了兩次拷貝構造函數構造了兩個臨時對象,一次是在instance函數返回時,一次在對t的初始化。 其實這兩個臨時對象並沒有什麼意義,構造完了馬上就析構了。但是就是因爲這麼兩個無用的東西,在拷貝構造函數中執行了不必要的內存拷貝,這裏還好,只是拷貝了100個int類型的對象,如果是拷貝100000個甚至更多了,可想而知效率是多麼的低了。

移動構造函數

C++是個追求效率的語言,因此絕不容許那麼低效的設計出現,有沒有可能將 在工廠函數當中所構造對象的成員變量(m_p)所指向的那塊內存“偷”過來,而不是重新開闢一塊內存,然後再將之前的內容複製過來呢? 這就是移動構造函數設計的思想。

所謂移動構造函數,大家從名字上應該可以猜到:它應該就是一種構造函數,只不過它接受的參數是一個本類對象的右值引用,對於本例,移動構造函數的定義如下:

Test(Test && rhs):m_p(rhs.m_p)
{
    rhs.m_p = nullptr;
}

可以看到,在移動構造函數的初始化列表中,只做了一個淺拷貝m_p(rhs.m_p),將rhs對象已經申請的內存據爲己用,同時將rhs的指針賦值爲nullptr。這就避免了拷貝構造函數內存複製導致的效率問題。拷貝構造函數和移動構造函數在實現時其內存的變化如下圖所示:
在這裏插入圖片描述
移動構造函數在用來構造臨時變量或者用臨時變量來構造對象的時候被調用,比如說,如果上面的例子在類中定義了移動構造函數,那麼例中調用拷貝構造函數的那兩處地方則應該調用移動構造函數。代碼如下:
在這裏插入圖片描述
在這裏插入圖片描述

std::move

現在我們來看另外一種場景,在下面的情況下,我們知道在Test t2(t1)處會調用拷貝構造函數(t1是左值,因此不會調用移動構造函數),那麼有沒有一種辦法在此處也調用移動構造函數而不是拷貝構造函數呢?

int main() 
{
	Test t1;
	// ......
	Test t2(t1);// ......
}

答案是肯定的,C++11標準中給我們提供了std::move來解決這個問題,如下,只需將Test t2(t1)換成下面的語句即可:

Test t2(std::move(t1));

這個std::move的作用就是將左值轉換爲右值,以便調用移動構造函數。

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