參考文章:
C++ Rvalue References Explained By Thomas Becker
Universal References in C++11 by Scott Meyers
寫在前面:
這個系列的程式碼與例子大多出自以上二篇文章,再加上自己本身的理解所描述而成,因此可能會有謬誤的地方還請見諒。若有什麼意見或想法,非常歡迎討論與指教。
本文開始
首先讓我們來看看下面這一段code:
template <typename T> void f(T&& param);
我們知道T&指的pass by eference,但T&&到底是?pass by reference的reference?就語法的理解上,似乎有點不太明確的知道它到底在做什麼,因為C++並沒有這種概念。為了跳脫這個語法表示上理解的困境,我們並不需要糾結於「&」這個語法的意義上,「&&」這東西並不是字面上是pass by reference的reference,就把「&&」他當成新的語意來理解吧!
其實T&&這個語法,是在C++11的時候才導入的,它主要可以解決以下幾個問題:
- Implementing move semantics
- Perfect forwarding
這裡先還不用急著理解這二個問題到底是什麼,下面的文章將會入深入的討論。但我們只要先知道:「在解決這二個問題之後,在程式中傳遞參數將能夠省下許多記憶體copy的時間」ーー這件事就行了。簡單來說,就是你的程式會變得更快。它能夠保證讓任何type的參數傳入可以用pass by reference的形式來處理,藉以減少傳遞參數時必須要從記憶體copy一份到新的參數上的開銷。Scott Meyers用Universal References來形容T&&這個語法實在是很巧妙,因為這個命名精確的傳達出了它的邏輯與用途。(c++11標準是沒有對它命名的,只有描述)
但在開始深入探討Universal References之前,我們必須先理解何謂左值(lvalue)與右值(rvalue)。在這裡我並不打算做詳細的定義,因此只會給個簡單的概念性描述:所謂的左值(lvalue),指的就是在賦與(assign)變數數值的時候,能夠出現在左邊或右邊的變數;而右值(rvalue),則是只能出現在右邊的數字。換另外一種說法就是,lvalue能夠接受並儲存一個數字,並將這個數字賦與給其他的lvalue;而rvalue則是只能將該值賦與給其他的lvalue。請參考下面的code:
int a = 42; int b = 43; // a and b are both l-values: a = b; // ok b = a; // ok a = a * b; // ok // a * b is an rvalue: int c = a * b; // ok, rvalue on right hand side of assignment a * b = 42; // error, rvalue on left hand side of assignment
在這裡a跟b是左值(lvalue),因此能夠出現在賦值符號(=)的左邊及右邊,但42、43以及(a*b)是右值(rvalue),因此只能出現在右邊。這裡比較需要思考的是,為什麼a跟b是左值,但a*b卻是右值。這是因為運算符號*其實也就是一個接受二個參數的function而已,如: int operator*(int left, int right);。function 無法賦值,因此是rvalue。當然,如果你取的是這個function回傳值的reference的話,那麼它又會是lvalue……。情況似乎變的有點複雜,讓我們來看看以下這篇文章的討論:http://stackoverflow.com/questions/13312817/foo-42-how-can-this-be-possible,裡面提到的例子:
int & foo() { static int ttt = 0; return ttt; } // lvalues: // int i = 42; i = 43; // ok, i is an lvalue int* p = &i; // ok, i is an lvalue int& foo(); foo() = 42; // ok, foo() is an lvalue int* p1 = &foo(); // ok, foo() is an lvalue
首先先把i設為42,再設為43,然後將i的reference指給p,這時p的任何更動就等同於更動i(以作業系統來看就是hard link的概念)。這時候有趣的東西來了:『int& foo(); 』。這到底是什麼意思?其實就是取foo回傳變數的reference。下一行當我們執行『foo() = 42』時,就等同於把foo裡面的ttt設為42。最後一行,就是把ttt的的reference指給p1。
當然,上面這個例子我們特別必須要注意到的就是ttt這個變數的生命週期了。若該變數沒有加上static的話,猜猜看會發生什麼事?ーー我不知道,當你試著去對一個記憶體已經被釋放的ttt(dangling reference)進行操作的時候會怎麼樣?大概,程式會死在那邊給你看吧?亦或者說那段時間沒有人去用到那塊記憶體,程式『一如往常』的執行,然後又非週期性的大爆炸。又或者說,有別的指標去指到那塊4byte的記憶體,結果程式去執行該指標所指到的function。ーー總之,那是個災難的開始。undefined behaviour,指的就是這種情況。若你的程式中曾經出現過這種薛丁格或海森堡式的bug時,可能要去思考一下是不是有dangling reference的存在。
Leave a Reply