C++11引用臨時變量的終極解析

工做中遇到一個引用臨時變量的問題,通過兩天的學習,私覺得:不只弄明白了這個問題,還有些本身的獨到看法。
這裏使用一個簡單的例子來把本身的學習過程和理解獻給你們,若是有什麼問題請不吝指正。
 
*************************Code*************************
 
class  Dog
{
public:
     Dog(){}
     virtual ~ Dog(){}
};
 
void  NonConstReference ( Dog &  dog )
{
     //tell the dog to do something here
}
 
void  TestNonConstReference ()
{
     NonConstReferenceDog());
}
 
*************************VS 2013, Level4 (/W4)*************************
 
warning C4239: nonstandard extension used : 'argument' : conversion from 'Dog' to 'Dog &'     
 
*************************GCC, C++11*************************
-------------- Build: Debug in Test (compiler: GNU GCC Compiler)---------------
 
mingw32-g++.exe -Wall -fexceptions -g -std=c++11  -c G:\MyBackup\code\CodeBlock\Test\main.cpp -o obj\Debug\main.o
G:\MyBackup\code\CodeBlock\Test\main.cpp: In function 'void TestNonConstReference()':
G:\MyBackup\code\CodeBlock\Test\main.cpp:18:29: error: invalid initialization of  non-const reference of type  'Dog&' from an  rvalue of type  'Dog'
G:\MyBackup\code\CodeBlock\Test\main.cpp:11:6: error: in passing argument 1 of 'void NonConstReference(Dog&)'
Process terminated with status 1 (0 minute(s), 0 second(s))
2 error(s), 0 warning(s) (0 minute(s), 0 second(s))
 
*************************lvalue, xvalue, prvalue的通常定義*************************
首先lvalue, rvalue 都是針對表達式的;任何一個表達式均可以按照以下歸類方式歸類:
lvalue指代一個函數或者對象。例如: 
  1. E是指針,則*E是lvalue
  2. 一個函數的返回值是左值引用,其返回值是lvalue。例如int& foo();
 
xvalue指代一個對象,可是和lvalue不一樣,這個對象即將消亡。
 
prvalue指代一個臨時對象、一個臨時對象的子對象或者一個沒有分配給任何對象的值。例如:
  1. 一個函數的返回值是日常類型,其返回值是rvalue。例如int foo();
  2. 沒有分配給任何對象的值。如5.3,true。
*************************lvalue, xvalue, prvalue的區分*************************
 說明:這部分來自C++ PROGRAMMING LANGUAGE 4TH EDTION。
 
There are two properties that matter for an object when it comes to addressing, copying, and moving:
•  Has identity: The program has the name of, pointer to, or reference to the object so that it is possible to determine if two objects are the same, whether the value of the object has changed, etc.
•  Movable: The object may be moved from (i.e., we are allowed to move its value to another location and leave the object in a valid but unspecified state, rather than copying;).

It turns out that three of the four possible combinations of those two properties are needed to precisely describe the C++ language rules (we have no need for objects that do not have identity and
cannot be moved). 
 
Using ‘‘m for movable’’ and ‘‘i for has identity,’’ we can represent this classification of expressions graphically:

So, a classical lvalue is something that has identity and cannot be moved (because we could examine it after a move), and 
a classical rvalue is anything that we are allowed to move from. 

 
*************************ISO IEC 14882 2011  8.5.3 References*************************
 
ISO文檔使用cv來表明const volatile 修飾符。
而且假設咱們使用這樣的一種方式來賦值:cv1 T1 dest = cv2 T2 src;
 
舉個例子就是:
int src = 123;
const int& dest = src;
 
void function(const int& dest){};
function(src);
 
ISO文檔首先給出了兩個概念:reference-related, reference-compatible。
Given types 「 cv1 T1」 and 「 cv2 T2,」 「 cv1 T1」 is  reference-related to 「 cv2 T2」 if 
  1. T1 is the same type as T2, or
  2. T1 is a base class of T2. 
 
cv1 T1」 is  reference-compatible with 「 cv2 T2」 if 
  1. T1 is reference-related to T2 and 
  2. cv1 is the same cv-qualification as, or greater cv-qualification than, cv2
說明:cv1 >= cv2的狀況都有哪些呢:const > 沒有修飾符, const volatile > const,etc.
 
分析一次賦值:cv1 T1 dest = cv2 T2 src; 是否合法採用以下4個步驟:
1.若是dest 是一個lvalue reference,同時:
     1.1若是src是一個左值(不是一個bit-filed),而且cv1 T1 是 reference-compatible with cv2 T2的;
     1.2若是T2是一個類類型(class, struct, union, etc.),即便cv1 T1  不是 reference-compatible with cv2 T2的,只要cv2 T2能夠被轉換成cv3 T3類型的一個左值(src1),這時若是cv1 T1 是 reference-compatible with cv3 T3的;
那麼,dest 就幫定到src,或者src1上。
 
2.若是cv2 T2 src不能知足1.1,1.2,那麼cv1 就應該是一個包含const的lvalue reference定義,不然它就因該是一個rvalue reference。此時若是cv2 T2知足以下條件:
     2.1若是src是一個xvalue, 類類型的prvalue, array prvalue 或者返回左值的函數,而且cv1 T1 是 reference-compatible with cv2 T2的;
     2.2若是cv2 T2是類類型的,即便cv1 T1  不是 reference-compatible with cv2 T2的,只要cv2 T2能夠被轉換成cv3 T3類型的一個2.1規定的值,假設是src1;
那麼,dest就幫定到src,或者src1上。
 
3.若是cv2 T2 src也不能知足2.1,2.2,那麼編譯器就爲src建立一個臨時變量。
     3.1建立此臨時變量的條件是:cv1 T1 是 reference- related with cv2 T2,而且cv1 >= cv2;
 
4.若是cv2 T2 src不能知足上面全部的條件,那麼cv1 T1就應該是一個rvalue reference。此時,若是cv2 T2是一個lvalue的話,編譯器應該抱錯。
 
*************************Reference 匹配(過濾)過程*************************
 
 
**************************************這裏有些例子**************************************
 
-------------------------------能被規則1處理完畢-------------------------------------------------
      double d = 2.0;

     double& rd = d; //d, is an lvalue, and the cv1 equals cv2, 1.1可以處理
 
     const double& rcd = d; // d, is an lvalue,
                                      // the cv1 >= cv2: const > 沒有修飾符,1.1可以處理
 
     struct A { };
     struct B : A 
     { 
          operator int&(); 
     } b;
     
     A& ra = b; // b, has a class type: struct;
                     //cv1 is reference related with cv2, ra is the base class of the b,1.2可以處理
 
     const A& rca = b; // b, has a class type, struct;
                              //cv1 is reference related with cv2, ra is the base class of the b;
                              // the cv1 >= cv2: const > 沒有修飾符,1.2可以處理
 
     int& ir = B(); // B(), has a class type: struct
                        //it can be converted to an lvalue of the int&: operator int&() 
                        //cv1 == cv2: cv修飾符都是空,1.2可以處理
 
------------------------------不符合規則1,被規則2處理-----------------------------------------
     
extern B f();
     

      const A& rca2 = f();// f()返回值是一個類類型的rvalue,c++

                                  // the cv1 >= cv2: const > 沒有修飾符,2.1可以處理 
            struct X {
          operator B();
          operator int&();
      } x;

     const A& r = x;// x 是類類型的
                          // r 與x 不是reference-compatible的
                          // x 經過operator B()返回一個類類型的prvalue, tmpB
                          // r 與tmpB 的關係知足2.1的條件,2.2可以處理
 
-----------------------不符合規則1,也不符合規則2,被規則3處理---------------------------
 
     const double& rcd2 = 2; // 2,不是一個lvalue/xvalue/類類型的prvalue/函數返回的左值,等。
                                  // 建立一個臨時變量2.0,3可以處理
 
--------不符合規則1,也不符合規則2,也不符合規則3,被規則4處理----------------------
 
     double d2 = 1.0;
     double&& rrd2 = d2; // rrd2是一個rvalue reference,不能使用lvalue 賦值。4可以處理
 
 
-----------------------------------------其餘一些例子-------------------------------------------------------------
     const volatile int cvi = 1;
     const int& r2 = cvi; // error, in this  example, the cv1 <= cv2, which violate the 1.1 
 
 
*************************回到咱們的例子*************************
 
class  Dog
{
public:
     Dog(){}
     virtual ~ Dog(){}
};
 
void  NonConstReference ( Dog &  dog )
{
     //tell the dog to do something here
}
 
void  TestNonConstReference ()
{
     NonConstReferenceDog()); 
}
 
NonConstReferenceDog())調用,在棧上建立了一個類類型的prvalue。
根據ISO文檔,它不能規則1接納,就只能由規則2繼續處理。
 
規則2要求 NonConstReference( Dog &  dog ) 中的Dog & dog 必須是const Dog & dog。
 
而這裏顯然不是,因此抱錯。
 
************************編譯器爲咱們做了什麼?語義分析*****************************
 
編譯器,在嚴格的按照,c++語言的設計來執行語義檢查:
  1. 目標是一個lvalue reference, 那麼就不能給我一個rvalue.
  2. 要麼就把目標設置成const lvalue reference.
 
若是一個參數是以非const引用傳入,c++編譯器就認爲程序在函數中修改這個值,而且想要這個被修改過的值。
 
但若是你把一個臨時變量看成非const引用參數傳進來,程序並無機會繼續訪問這樣的變量,從而使修改一個臨時變量變得毫無心義的。
 
從而c++編譯器加入了臨時變量不能做爲非const引用的這個語義限制,意在限制這個很是規用法的潛在錯誤。
 
**************************************完*******************************************
相關文章
相關標籤/搜索