工做中遇到一个引用临时变量的问题,通过两天的学习,私觉得:不只弄明白了这个问题,还有些本身的独到看法。
这里使用一个简单的例子来把本身的学习过程和理解献给你们,若是有什么问题请不吝指正。
*************************Code*************************
class
Dog
{
public:
Dog(){}
virtual ~
Dog(){}
};
void
NonConstReference (
Dog &
dog )
{
//tell the dog to do something here
}
void
TestNonConstReference ()
{
NonConstReference(
Dog());
}
*************************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指代一个函数或者对象。例如:
- E是指针,则*E是lvalue
- 一个函数的返回值是左值引用,其返回值是lvalue。例如int& foo();
xvalue指代一个对象,可是和lvalue不一样,这个对象即将消亡。
prvalue指代一个临时对象、一个临时对象的子对象或者一个没有分配给任何对象的值。例如:
- 一个函数的返回值是日常类型,其返回值是rvalue。例如int foo();
- 没有分配给任何对象的值。如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
- T1 is the same type as T2, or
- T1 is a base class of T2.
“
cv1 T1” is
reference-compatible with “
cv2 T2” if
- T1 is reference-related to T2 and
- 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 ()
{
NonConstReference(
Dog());
}
NonConstReference(
Dog())调用,在栈上建立了一个类类型的prvalue。
根据ISO文档,它不能规则1接纳,就只能由规则2继续处理。
规则2要求
NonConstReference(
Dog &
dog )
中的Dog & dog 必须是const Dog & dog。
而这里显然不是,因此抱错。
************************编译器为咱们做了什么?语义分析*****************************
编译器,在严格的按照,c++语言的设计来执行语义检查:
- 目标是一个lvalue reference, 那么就不能给我一个rvalue.
- 要么就把目标设置成const lvalue reference.
若是一个参数是以非const引用传入,c++编译器就认为程序在函数中修改这个值,而且想要这个被修改过的值。
但若是你把一个临时变量看成非const引用参数传进来,程序并无机会继续访问这样的变量,从而使修改一个临时变量变得毫无心义的。
从而c++编译器加入了临时变量不能做为非const引用的这个语义限制,意在限制这个很是规用法的潜在错误。
**************************************完*******************************************