C++ 是否能夠定義引用的引用?

C++ Primer第五版里說

「因為引用本身不是一個對象,所以不能定義引用的引用」。

可是我的代碼已經通過了編譯,是我對「引用的引用」理解錯了嗎?

int main(){
int ival=1024;
int refVal=ival;
int (refVal2)=refVal;
return 0;
}


1)你貼的代碼確實是「引用的引用」,但你的截圖不是:截圖中的refVal和refVal2都是引用。

2)你的編譯器「偷偷」修改了標準:嚴格符合標準的編譯器是不允許直接定義引用的引用的而只允許間接定義。

間接定義引用的引用有兩種方式:

a)類型別名。

例如:

using intRef = int ; // 用typedef亦可
int a = 1;
intRef rrefa = a; // rrefa是引用的引用

b)模板。

例如:

template&
void func(T t){}

int a = 1;
func(a);
/***************** 注意 *****************/
/* 如果func是普通函數而非函數模板,則這段代碼會報錯 */
/* 因為左值不能綁定到右值引用上 */
/* void func(int t){} ---func(a) --- ERROR!!! */

/* 函數模板是個特例,此處會觸發引用摺疊 */

引用摺疊規則:

X (引用的引用)、X (右值引用的引用)、X (引用的右值引用)均摺疊為X 。

X (右值引用的右值引用)摺疊為X 。

上面的類型別名和函數模板均觸發了引用摺疊。

注意:引用摺疊的前提必須是類型別名或者模板參數。標準禁止直接定義引用的引用。

為什麼需要引用摺疊:

引用摺疊是std::move、std::forward等的工作基礎。

所以題主千萬不要覺得「引用的引用」是一件「很正常」的事情。如果有人問你:c++能定義引用的引用嗎?答案是:不能。不過你可以補充說:不過有兩個例外:類型別名和模板參數時可以間接定義引用的引用。

事實上,所謂「間接定義引用的引用」的說法並不十分正確,正確的說法是「引用摺疊」而其實引用摺疊後依舊是普通的引用或者右值引用,所以其實「引用的引用」嚴格來說是不存在的。c++需要引用摺疊的原因是為了實現std::move。也就是說,所謂「引用的引用」的存在價值,只是為std::move、std::forward等而服務的,僅此而已;它並不是讓程序員去用的。在你自己寫代碼的時候,一定不要「引用的引用」---因為凡是需要用「引用的引用」即引用摺疊才能實現的代碼,你都可以在std里找到,例如std::move、std::forward。

說到這裡,可能會有朋友突然靈機一動:如果我把所有的函數模板的參數類型都定義成T ,那麼這個模板就既可以接受左值實參又可以接受右值實參,豈不是很好嗎?

這個想法看似很不錯但是是不可行的,比如:

template&
void func(T param)
{
T t = param; // 普通的賦值拷貝還是引用?
t = changeValue(t); // param的值也會被改變嗎?
if (t == param) // 是不是永遠為true?
{//...}
}

在個例子里,如果給func一個左值,比如int,那麼T就會被推斷成int ,param的類型經過引用摺疊後是int 。此時,T t就等效於int t,因此t是引用;而如果給func一個右值,那麼T就會被推斷為int。此時,T t等效於int t,因此t是賦值拷貝後生成的T類型的一個普通值。

所以,在大多數情況下,寫模板還是老老實實按部就班地寫,千萬不要濫用引用摺疊。再說一次,引用摺疊是服務於std::move、std::forward等的,而並不是服務於程序員的。

不過,在少數情況下,程序員確實需要使用引用摺疊來完成一些代碼的設計。

「少數情況」指:

case1)模板轉發實參

case2)模板重載

這裡就不展開說了。

關於這部分內容的推薦:

《c++ primer》第五版(中文版)---P608至P612

另:如果題主是在學習c++,那麼請你務必儘快使用靠譜的編譯器,你截圖顯示出你在用一款非常粗糙的編譯器(本來應該報錯的但是沒有),這是很可怕的因為這些編譯器會篡改c++的標準。這裡給題主推薦用gcc,如果實在不習慣gcc,那麼也可以下載個eclipse寫c++。(Visual Studio也可以,不過vs會做很多優化工作,特別是對右值的優化,右值的生命(以及一些本該在return後就結束的變數的生命)在vs里經常會很長---超越了標準地長,所以雖然vs是款強大的IDE,不過其實並不適合用來學習c++)

如果你實在找不到合適的編譯器,你甚至可以找個靠譜點的在線c++編譯器---即使是這種編譯器都要比你現在使用的編譯器要強得多得多。這裡給你推薦一個,但不知是否需要翻牆:

Compile and Execute C++ Online


ISO C++11 8.3.2

5 There shall be no references to references


Effective Modern C++, Chapter 5, Item 28: Understand reference collapsing

You are forbidden from declaring references to references, but compilers may produce them inparticular contexts.

When compilers generate references to references, reference collapsing dictates what happens next.

Reference collapsing occurs in four contexts: template instantiation, auto type generation, creation and use of typedefs and alias declarations, and decltype.

When compilers generate a reference to a reference in a reference collapsing context, the result becomes a single reference. If either of the original references is an lvalue reference, the result is an lvalue reference. Otherwise it』s an rvalue reference.


// C++ 不可以。

知乎也不可以。


c++11支持reference collapse,就是:

  • =&>
  • =&>
  • =&>
  • =&>


引用不是一種數據類型,故而不存在引用的引用。

int ival=1024;

int refVal=ival;

此時,refVal的類型是int,它是ival的引用。


有引用的引用,但是沒有引用的指針,引用不是一個對象是一個別名。


推薦閱讀:

電腦一點不會的傢伙怎麼學編程?看什麼書?
如何學習編譯原理?
JVM里的符號引用如何存儲?

TAG:編程 | 計算機科學 | C |