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,

                                  // 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引用的這個語義限制,意在限制這個非常規用法的潛在錯誤。
 
**************************************完*******************************************


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM