C++——“非常量引用的初始值必須爲左值”問題

參考:http://blog.sina.com.cn/s/blog_6d6f47690101dgwi.html
改正了原博文的某些疏漏。

一個栗子

看下面一個例子:

int main(){
    int i = 2;
    double &r = i;
    return 0;
}

報錯:gcc error: invalid initialization of reference of type ‘double&’ from expression of type ‘int’

如果改成 const double &r =i;沒有問題。難道這裏的i不是左值?

程序改成:

int main(){
    double i = 2;
    double &r = i;
    return 0;
}

沒有錯誤。難道這裏的i又是左值了?

其實對於第一段代碼:const double &r = i;

由於類型不匹配會自動將變量i從int型轉爲double型,實際相當於:

const double inner_tmp = (double)i;  //這裏就產生了一個臨時變量
double &r = inner_tmp;

臨時的中間變量都是const,所有沒有const的引用會失敗。試想,如果這種操作合法,則我們可以通過引用更改常量的值。

左值與右值的區分

對左值和右值的一個最常見的誤解是:等號左邊的就是左值,等號右邊的就是右值。

其實,左值和右值都是針對表達式而言的,左值是指表達式結束後依然存在的持久對象,右值是指表達式結束時就不再存在的臨時對象。一個區分左值與右值的便捷方法是:看能不能對表達式取地址,如果能,則爲左值,否則爲右值。

比如說:int a = 10;

這裏的a就是左值:顯然a是可以被取地址的;

而數字常量10則是右值,因爲我們不可能取10的地址。

什麼是左值引用?

區分清楚了左值與右值,我們再來看看左值引用。左值引用根據其修飾符的不同,可以分爲非常量左值引用(double &r =i;)和常量左值引用(const double &r =i;)。

非常量左值引用只能綁定到非常量左值,不能綁定到常量左值、非常量右值和常量右值。試想,如果允許綁定到常量左值和常量右值,則非常量左值引用可以用於修改常量左值和常量右值,這明顯違反了其常量的含義。如果允許綁定到非常量右值,則會導致非常危險的情況出現,因爲非常量右值是一個臨時對象,非常量左值引用可能會使用一個已經被銷燬了的臨時對象。

常量左值引用可以綁定到所有類型的值,包括非常量左值、常量左值、非常量右值和常量右值。

爲什麼要區分非常量右值引用?

可以看出,使用左值引用時,我們無法區分出綁定的是否是非常量右值的情況。那麼,爲什麼要對非常量右值進行區分呢,區分出來了又有什麼好處呢?這就牽涉到C++中一個著名的性能問題——拷貝臨時對象。考慮下面的代碼:

vector<int> GetAllScores()
{
vector<int> vctTemp;
vctTemp.push_back(90);
vctTemp.push_back(95);
return vctTemp;
}

當使用vector vctScore = GetAllScores()進行初始化時,實際上調用了三次構造函數。儘管有些編譯器可以採用RVO(Return Value Optimization)來進行優化,但優化工作只在某些特定條件下才能進行。可以看到,上面很普通的一個函數調用,由於存在臨時對象的拷貝,導致了額外的兩次拷貝構造函數和析構函數的開銷。當然,我們也可以修改函數的形式爲void GetAllScores(vector &vctScore),但這並不一定就是我們需要的形式。另外,考慮下面字符串的連接操作:

string s1("hello");
string s = s1 + "a"+ "b"+ "c"+ "d"+ "e";

在對s進行初始化時,會產生大量的臨時對象,並涉及到大量字符串的拷貝操作,這顯然會影響程序的效率和性能。怎麼解決這個問題呢?如果我們能確定某個值是一個非常量右值(或者是一個以後不會再使用的左值),則我們在進行臨時對象的拷貝時,可以不用拷貝實際的數據,而只是“竊取”指向實際數據的指針(類似於STL中的auto_ptr,會轉移所有權)。C++ 11中引入的右值引用正好可用於標識一個非常量右值。C++ 11中用&表示左值引用,用&&表示右值引用,如:int&& a = 10;

右值引用根據其修飾符的不同,也可以分爲非常量右值引用和常量右值引用。

非常量右值引用只能綁定到非常量右值,不能綁定到非常量左值、常量左值和常量右值。如果允許綁定到非常量左值,則可能會錯誤地竊取一個持久對象的數據,而這是非常危險的;如果允許綁定到常量左值和常量右值,則非常量右值引用可以用於修改常量左值和常量右值,這明顯違反了其常量的含義。

常量右值引用可以綁定到非常量右值和常量右值,不能綁定到非常量左值和常量左值(理由同上)。可以看出,使用左值引用時,我們無法區分出綁定的是否是非常量。

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