[转]快速了解C/C++的左值和右值

定义###

早期的C给出的定义:左值是一个表达式,可能出现在赋值操作的左边或右边,但右值只能出现在右边。比如:
<pre>
a * b = 42; // 编译错误, 说明 a * b 不是左值
</pre>

因为上面的定义实在太模糊,导致左值和右值很难被理解,下面给出的定义,更简单更好理解:左值(lvalue)是一个表达式,它表示一个可被标识的(变量或对象的)内存位置,并且允许使用&操作符来获取这块内存的地址。如果一个表达式不是左值,那它就被定义为右值。
<pre>
int i = 42;
i = 43;
int* p = &i; // ok, i 是左值
int& foo();
foo() = 42; // ok, foo() 是左值
int* p1 = &foo(); // ok, foo() 是左值

int foobar();
int j = 0;
j = foobar(); // ok, foobar() 是右值
int* p2 = &foobar(); // 错误,不能获取右值的地址
j = 42; // ok, 42 是右值
</pre>

左值与右值之间的转换###

一般上讲,对象之间的运算,对象是以右值的形式参与的。比如二元运算符+两边的参数以右值传入,加后的返回结果也是右值:
<pre>
int a = 1; // a 是左值
int b = 2; // b 是左值
int c = a + b; // a和b自动转换为右值求和
</pre>

那些表示数组、函数和非完整类型的左值是不能转换为右值的,因为无法对那些类型进行求值。incomplete types指的是类型定义不完整,只能用指针形式声明的类型,在头文件中经常会使用。

左值引用###

C++中可以使用&符定义引用,如果一个左值同时是引用,就称为“左值引用”,如:
<pre>
std::string s;
std::string& sref = s; //sref为左值引用
</pre>
非const左值引用不能使用右值对其赋值
<pre>
std::string& r = std::string();//错误!std::string()产生一个临时对象,为右值
</pre>
假设可以的话,就会遇到一个问题:如何修改右值的值?因为引用是可以后续被赋值的。根据上面的定义,右值连可被获取的内存地址都没有,也就谈不上对其进行赋值。

但const左值引用不一样,因为常量不能被修改,也就不存在上面的问题:
<pre>
const std::string& r = std::string(); //可以
</pre>

我们经常使用const左值引用作为函数的参数类型,可以减少不必要的对象复制:
<pre>
class MyString
{
public:
MyString &MyString(string& s); //参数类型为左值引用
};

int main()
{
MyString s1("XXX"); //错误
MyString s2(string("XXXX")); //同上,右值不能赋值给左值引用
}
</pre>

带CV限定符(CV-qualified)的右值###

C++标准中关于左值转右值的讨论,有这样一段话:

类型为T的左值(非函数、非数组类型)可以被转换为右值。如果T不是类(class)类型,转换后的右值的类型将为不带CV限定符的T类型,否则转换后的右值的类型为T。

什么是CV限定符?如果变量声明时类型前带有const或volatile,就说此变量类型具有CV限定符。

在C中,右值永远没有CV限定符,而C++中的类类型的右值可以有CV限定符,看下面代码:

<pre>
class A
{
public:
void foo() const { std::cout << "A::foo() const\n"; }
void foo() { std::cout << "A::foo()\n"; }
};

A bar() { return A(); } //返回临时对象,为右值
const A cbar() { return A(); } //返回带const的右值(带CV限定符)

int main()
{
bar().foo(); // 非const对象调用A::foo()的非const版本
cbar().foo(); // const对象调用A::foo()的const版本
}
</pre>

也就是说,如果是类类型,从左值转为右值时,它的CV限定符会被保留。这里就不给出示例代码了。

右值引用(C++11)###

右值引用及其相关的move语义是C++11新引入的最强大的特性之一。前文说到,左值(非const)可以被修改(赋值),但右值不能。但C++11引入的右值引用特性,打破了这个限制,允许我们获取右值的引用,并修改之。让我们先看点代码: 定义一个类Intvec及其赋值操作符重载函数如下:
<pre>
class Intvec
{
public:
...
Intvec& operator=(const Intvec& other)
{
log("copy assignment operator");
Intvec tmp(other); //构造一个临时对象,因为other为const,不能被修改
std::swap(m_size, tmp.m_size);
std::swap(m_data, tmp.m_data);
//跟临时对象交换值,临时对象晰构时会delete [] m_data
return this;
}
private:
size_t m_size;
int
m_data; //存放int数组,构造时动态分配
};
</pre>

代码要点:

  • 代码使用了copy-swap策略,即先分配资源再更改自身状态,这样可以保证当资源分配失败的时候,自身能够维持原先状态,《高效C++》有条规则描述这个主题。所以先根据other拷贝构造一个临时对象tmp,然后与tmp进行swap,m_data交换给了tmp之后,也会随着tmp的晰构而被释放。
  • 之所以把other声明为const,有两个理由,其一是赋值操作不应该更改other,其二是可以传入一个右值。其实这样的声明随处可见。

假设现有类型为Intvec的对象v,用一个新对象给它赋值:
<pre>
v = Intvec(33);
</pre>

这句代码合法,它构造一个临时对象,为右值,传入到Intvec的赋值运算符重载函数中。这个代码是可以工作,而且通常情况下都比较高效。但是如果Intvec里包含某些m_handle成员,创建和释放m_handle比较昂贵,那么拷贝构造越少越好。这种情况,我们设想一下,如果v能跟Intvec(33)临时对象直接进行内部数据交换,而不需要在重载函数里使用Intvec tmp(other);构造一个新对象出来swap,那该有多好!

如你所料,C++11引入的“右值引用”和“move语义”就可以实现这个目标,新的语法很简单,我们重载一个新的赋值操作运算符函数:
<pre>
Intvec& operator=(Intvec&& other)
{
log("move assignment operator");
std::swap(m_size, other.m_size);
std::swap(m_data, other.m_data);
return *this;
}
</pre>
对于v = Intvec(33);这种写法就会调用此版本的重载函数(即传入一个右值)。
&&语法声明右值引用,表示一个指向右值的引用,通过这个引用,可以修改右值。

以上就是关于右值引用的一个简单的示例,实际上右值引用是一个复杂的主题,在实际应用中还有很多场景要考虑。


原文链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容