再看C++引用類型

以前棄用博客園的緣由是其不支持markdown語法。到今天偶然進來試了一下,發現Markdown toggle原來是能支持的(不知道是否是由於它升級了),遂從新啓用。c++

在一年前學C++的時候就對引用,常引用和右值引用迷迷糊糊的,再加上老師實在是不給力啊!(當時我問她一個很基礎的關於函數對象的問題,她問我,什麼是函數對象)因此那時候就只知道使用慣例而不知其具體內涵。再加上近一年時間都在其餘語言上花費精力,對它們的區別就更加模糊了。直到前幾天在stackoverflow上問了個關於SFINAE的問題,才猛地感受到本身的C++徹底要回爐重練了。今天從新看回C++ Prime Plus,終於有了一種恍然大悟的感受了。程序員

爲什麼要接受引用爲參數,爲什麼要返回引用?

既然引用參數/返回值的使用常出如今類的定義裏,就以類爲例:安全

class C {
    int data[1024];
}

C foo(C c) { /* do sth */; return c; }
const C& foo(C& c) { /* do sth */; return c; }

若是是以值來傳遞參數,返回值,那麼一個函數的調用須要經歷這幾步:markdown

C a;
C b = foo(a);

a => temp val (as arg) => arg c => temp val (as return value) => temp val (as arg in constructor) => bapp

咱們能夠用c++語句來描述上述過程函數

a => tmpa = a => arg c => tmpc = c => tmpb = tmpc => bui

從a到tmpa的過程天然是爲了保證值傳遞的語義,使得函數內對參數的修改不會影響到傳入的變量自己,亦即不產生反作用。而從c到返回值tmpc的過程,當c是臨時變量時,將發生」Named Return Value Optimization「。現代的編譯器在返回以前將看到被返回的是一個臨時變量且變量生命週期將隨着離開函數而終結,所以編譯器將變量轉移到上層scope的臨時對象中加以保存。編譯器將根據變量提供的copy/move構造器決定這一過程是經過copy仍是move來完成。url

顯然,從實參到形參的拷貝,和返回值時的拷貝過程是徹底能夠避免的。方法就是將參數/返回值類型由值改成引用。這麼一捋,引用類型參數所引用的對象,和返回值所引用的對象就很是清晰了。參數引用的對象天然是實參,而返回值的引用對象則是返回語句中返回的對象。也就是:spa

C& tmpa = a; // passing argument
const C& tmpc = c; // returning result
const C& tmpb = tmpc; // construct b

參數/返回值的傳遞就至關於上述語句。天然地,對於引用類型的限制的特性也一樣做用於參數和返回值。好比:code

  • 很是引用不能引用臨時對象,天然地,不能引用表達式(包括lambda表達式)
  • 常引用引用臨時對象時將產生警告

也就是說,不管如何,左值引用都沒法引用表達式(由於表達式的結果是臨時對象,即右值)。正是由於引用類型參數和本來的值類型參數在此行爲上的差別。便產生了可以引用表達式的引用——右值引用。若是你但願你的函數在接受參數時具備和接受值類型參數同樣的行爲,又想避免拷貝開銷,就將參數類型聲明爲右值引用。

如今將臨時對象傳遞給引用參數的問題解決了,那麼返回值呢?如何返回一個臨時變量,且避免拷貝開銷?一個可選方案是,將接受返回結果的變量引用傳遞進函數內。事實上不少庫函數就是採用這樣的方法的。下面是一個簡單的例子:

const int& add(const int& a, const int& b)
{ return a + b; } // warning: returning reference to local temporary object
void add(const int& a, const int& b, int& r)
{ r = a + b; } // OK

當傳入參數爲右值引用時,在函數體內並不會被看做臨時對象,這是出於安全的考慮。由於參數在函數內可能被引用屢次,而極可能其中一次的引用就「would be free to rip its guts out」掉了,這樣的話接下來的對它的引用就都變成非法的了。正由於如此,程序員須要使用std::move函數來明確指定在哪一個地方能夠將它看做臨時對象。好比在下面的例子裏,在函數中,調用dosth時傳入的是a的副本,而在dosthelse裏則能夠放心傳入a自己了。

C foo(C&& a)
{
    dosth(a);
    dosthelse(std::move(a));
}

其實從clang在返回臨時對象給引用的編譯警告-Wreturn-stack-address咱們就能大概看出左值和右值的區別所在了。右值和左值的根本區別就在於右值不能提供經過地址訪問在任什麼時候刻的有效性。天然也就沒法被安全引用了。

相關文章
相關標籤/搜索