參考文章:
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的存在。
Mike Curtis
Access premium B2B data—completely free of charge.
Whether you’re targeting lawyers in New York, construction firms across the globe, restaurants in Cape Town, or financial institutions worldwide, our extensive database has you covered.
Simply tell us your specific requirements, and we will deliver the curated dataset directly to you, at absolutely no cost.
https://Mike.verifieddata.one