C++雾中风景10:聊聊左值,纯右值与将亡值

C++11的版本在类型系统上下了很大的功夫,添加了诸如auto,decltype,move等新的关键词来简化代码的编写与降低阅读代码的难度。为了更好的理解这些新的语义,笔者确定通过几篇文章来简单窥探一下C++类型系统的冰山一角,如果加深了对C++类型系统的理解,想必大家也能够更好的应用由C++11带给我们的新"利器"。

1.左值与右值

左值(lvalue)和右值(rvalue)是C++类型系统之中的基础概念,我们不需要了解这些基础概念,同样也能写出代码。但是如果没有弄清左右值的概念,对于许多C++高级特性的探索会一叶障目,所以笔者尝试总结一下自己对于左值与右值的理解。

在C++11之前的版本,基本沿用了C语言之中对于左值与右值的定义,说起来也很简单:“在C++之中的变量只有左值与右值两种:其中凡是可以取地址的变量就是左值,而没有名字的临时变量,字面量就是右值”。 正是因为这两种变量分别位于=的左右两侧,所以被命名为左值与右值。下面,举个栗子:

int x;
int y;

x = 1;
y = 2;
x = y;
y = x;

// 以下代码有误
3 = x;
x + y = 4;

通过上述的代码我们可以快速的理解,显然x,y作为变量可以存在=的左侧,而称之为左值,而3,x + y作为字面量或中间结果,没有办法取得地址,则称之为右值。 这里笔者也给一个简单判定的左右值的方式: 判断能否取值的地址,能取地址的就是左值。

2.将亡值

其实上一节对于左值右值的描述,在我们编写绝大多数代码的场景下并没有什么影响。而在C++11扩展了右值的的概念,将右值分为了纯右值(pure rvalue)将亡值(eXpiring Value)。纯右值的概念等同于我们之前所理解的右值,指的是临时变量或字面量值;而将亡值是C++11新引入的概念,它依托于右值。

左值,纯右值与将亡值

在C++之中,使用左值去初始化对象或为对象赋值时,会调用拷贝构造函数或赋值构造函数。而使用一个右值来初始化或赋值时,会调用移动构造函数或移动赋值运算符来移动资源,从而避免拷贝,提高效率。 而将亡值可以理解为通过移动构造其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,来延长变量值的生命期。而实际上该右值会马上被销毁,所以称之为:将亡值

上述概念阐述的略微抽象,我们来看下面这段代码: 这是我们简单定义的Time类,在类中我们定义了拷贝构造函数和移动构造函数:

class Time {


public:
    int* hour;
    int* minute;
    int* second;

    Time(int h, int m, int s) {
        hour = new int(h);
        minute = new int(m);
        second = new int(s);
    }

    Time(const Time& t) {
        cout << "copy" << endl;
        hour = new int(*t.hour);
        minute = new int(*t.minute);
        second = new int(*t.second);
    }

    Time(Time&& t) noexcept:hour(t.hour),minute(t.minute),second(t.second) {
        t.hour = nullptr;
        t.minute = nullptr;
        t.second = nullptr;
        cout << "move" << endl;
    }

    ~Time() {
        cout << "call ~Time()" << endl;
        delete hour;
        delete minute;
        delete second;
    }

};

接下来我们执行下面的代码:

int main()
{
    Time test(10,25,12);
    Time test2(test);
    return 0;
}

执行结果:

  copy
  call ~Time()
  call ~Time()

由上述代码我们看到test2对象调用了拷贝构造函数来构造了新的对象,这个过程显然是更占用内存的。而接下来,我们尝试利用move函数将test强行转化为将亡值,来避免内存重新分配的过程。但是之后我们也无法再访问test对象的内容了,因为都在移动构造函数之中置为了空指针

int main()
{
    Time test(10,25,12);
    Time test2(move(test));
    return 0;
}

执行结果:

     move
     call ~Time()
     call ~Time()

通过这样的方式来减少不必要的内存操作。但是之后我们也无法再访问test对象的内容了,因为都在移动构造函数之中置为了空指针。将亡值通过移动构造函数”借尸还魂“,通过test2变量延续了自己的生命周期。

3.左值的一些"坑"

虽然笔者给出了左右值分辨的一些基本标准,但是还是有下面一些很让人迷惑的场景:

  • 条件表达式返回左值
true ? i : i;
  • ++
i++ // 左值
++i // 右值
  • []数组取值返回左值
i[10]
  • 指针取值操作符返回左值
*i
  • 字符串字面量返回左值
“hello world”

这是一些表示左值的特殊情况,这里笔者也不展开一一赘述了,希望大家可以简单的进行记忆。当然,笔者从来不去记一些太琐碎的问题,因为太他喵难记了,所以在C++11之中,可以标准库中添加的模板类is_lvalue_reference来判断表达式是否为左值,is_rvalue_reference来判断是否为右值。

  cout << is_lvalue_reference<decltype(i[10])>::value << endl;
  cout << is_rvalue_reference<decltype(i[10])>::value << endl;

返回1则为真,0为假。

4.小结

这只是我们对C++类型系统的第一篇探讨,后续笔者还会继续深入的探讨有关C++11新特性之中与类型系统相关的内容,欢迎大家多多讨论,指教。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏王亚昌的专栏

C++多线程编程学习二 [类中封装互斥量的设计]

      之前我也提到过,如果一个类的数据成员中在多线程环境中可能会被竞争使用时,一定要在类中解决这个问题,而不是在代码编写过程中在每次使用时去申请或释放,这...

9510
来自专栏magicsoar

Effective Modern C++翻译(1):序言

/*********************************************************** 关于书: 书是我从网上找到的effec...

20090
来自专栏xingoo, 一个梦想做发明家的程序员

二分搜索技术

分治法的基本思想:将一个规模为n的问题,分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。递归的解这些子问题,然后将各个子问题的解合并得到原问题的解...

25990
来自专栏互联网开发者交流社区

我个人对OOP的理解

10930
来自专栏小樱的经验随笔

【Java学习笔记之十三】初探Java面向对象的过程及代码实现

理解Java面向对象的重要知识点: 一、 类,对象 类?首先举一个例子:小李设计了一张汽车设计图,然后交给生产车间来生产汽车,有黑色的、红色的、白色的... ...

35260
来自专栏醒者呆

克隆一个对象——原型模式深入解析

原型模式也是创建型的设计模式,字面意思其实很简单,就是复制一个对象,这里面有什么学问呢? 用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。 ...

35270
来自专栏xingoo, 一个梦想做发明家的程序员

剑指OFFER之最小的K个数(九度OJ1371)

题目描述: 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。 输入: 每个测试案例包括...

21290
来自专栏程序员的SOD蜜

JavaScript的“原型甘露”

今天跟朋友讨论JS的面向对象编程问题,想起了原来曾经看过一篇文章,但是看过很久想不起来了,用了很多关键词,终于用“悟透JavaScript  面向对象”这两个关...

27480
来自专栏工科狗和生物喵

【计算机本科补全计划】C++牛客网试题习题解析

正文之前 一大早醒来,外面淅淅沥沥的雨绵绵的下着,床铺真的舒服,但是我也不能就在床上刷微博看小说吧,所以想起了昨晚下载的牛客网的APP,赶紧掏出我的大宝贝---...

40570
来自专栏带你撸出一手好代码

从PHP代码的细节说起

因为一个BUG, 我在一个摇摇欲坠,几乎碰一下就会散架的项目中某一个角落中发现下面这样一段代码 ? 这段程序与那个BUG有密切的关系。 我来回反复的捉摸这段代码...

46670

扫码关注云+社区

领取腾讯云代金券