Cpp / 右值、纯右值、将亡值

一、左值与右值

左值(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 作为字面量或中间结果,没有办法取得地址,则称之为右值。 这里笔者也给一个简单判定的左右值的方式:

判断能否取值的地址,能取地址的就是左值。

二、将亡值

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

左值,纯右值与将亡值

glvalue(泛左值) = lvalue (传统意义上的左值)+ xvalue(消亡值,通过右值引用产生)

rvalue (传统意义上的右值) = prvalue(纯右值) + xvalue

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

  • 返回右值引用的函数的调用表达式。
  • 转换为右值引用的转换函数的调用表达式。

上述概念阐述的略微抽象,我们来看下面这段代码:

这是我们简单定义的 Time 类,在类中我们定义了拷贝构造函数和移动构造函数:

#include <iostream>

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)
    {
        std::cout << "copy contruct." << std::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;
        std::cout << "move contruct." << std::endl;
    }
 
    ~Time()
    {
        std::cout << "call ~Time()" << std::endl;
        delete hour;
        delete minute;
        delete second;
    }
};

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

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

执行结果:

copy contruct.
call ~Time()
call ~Time()

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

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

执行结果:

move contruct.
call ~Time()
call ~Time()

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

三、拓展

1、std::move 的作用是无论你传给它的是左值还是右值,通过 std::move 之后都变成了右值。

2、++i 是左值,i++ 是右值。

前者,对 i + 1 后再赋给 i,最终的返回值就是 i,所以,++i 的结果是具名的,名字就是 i;而对于 i++ 而言,是先对 i 进行一次拷贝,将得到的副本作为返回结果,然后再对 i + 1,由于 i++ 的结果是对 i + 1前 i 的一份拷贝,所以它是不具名的。假设自增前i的值是 6,那么,++i 得到的结果是 7,这个 7 有个名字,就是 i ;而 i++ 得到的结果是 6,这个 6 是 i + 1 前的一个副本,它没有名字,i 不是它的名字,i 的值此时也是 7。可见,++i 和 i++ 都达到了使 i + 1的目的,但两个表达式的结果不同。

 

转自:https://www.cnblogs.com/happenlee/p/9337776.html

(SAW:Game Over!)

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值