c++学习-左值右值和std::move初探

本文记录我对左右值的学习,以及std::move语义的学习。

基本概念

何为左值,何为右值?

区分左值右值的真正说法是:能否用“取地址&”运算符获得对象的内存地址。也即左值其实关联了一块具体内存,但是右值没有。那么,右值又存在哪呢?
对于临时对象,它可以存储于寄存器中,所以是没办法用“取地址&”运算符;
对于常量,它可能被编码到机器指令的“立即数”中,所以是没办法用“取地址&”运算符;

int a = 3;
10 = a; // 错误,10是右值,它是常量。
int b = 5;
a * b = 10; // 错误,a*b是左值,它是临时对象,存储在寄存器当中。

move语义是什么

这个就比较麻烦了,因为直接说它没法说清楚,要结合右值引用才可以。暂时点到即止即可。
看如下代码:

class X;
X func(); // 返回一个X对象;
X obj;
obj = func(); // 赋值

当上述代码执行时,最后一句执行需要的操作时,假设临时对象已经生成:
1.销毁obj所持有的资源
2.拷贝临时对象的所以资源到obj
3.释放临时对象

一个更具有效率的做法是:
1. 交换obj和临时对象的资源指针
2. 让临时对象析构obj之前的资源
和上面相比,节省了一个构造和析构。
也即,左值直接接管右值的资源,左值之前的资源被析构。有点类似改头换面的感觉。

// swap m_pResourse and rhs.m_pResourse
// 析构左值对象

这就是所谓的move语义。
下面给出cplusplus给出的定义:

Many components of the standard library implement move semantics, allowing to transfer ownership of the assets and properties of an object directly without having to copy them when the argument is an rvalue.

这句话说明了,移动语义是这样的一种情形:当对象是右值时,允许左值转移右值的资源和属性而不用进行拷贝复制。
此时右值所处的状态:in a valid but unspecified state

右值引用

如果X是一种类型,那么X&&就叫做X的右值引用。为了更好的区分两,普通引用现在被称为左值引用。

为什么要搞一个右值引用出来,他和move语义又是什么关系?我们从上面的move语义已经看出来,一个应用场景是返回值在进行拷贝构造的时候可以避免生成一个新的对象,左值“窃取”了右值的内容,改头换面继续存活。我的理解就是,move语义主要是用来窃取右值的内容,可是,怎么识别右值这个东西呢?因为所有的参数类型都是实际意义上的左值参数类型,所以,我们是可以识别右值,但是你要告诉机器什么是右值,就需要引入右值引用这个东西。
1. 如果一个变量是右值
2. 那么再进行拷贝货值赋值的时候,可以不执行拷贝或者赋值的语义。而是执行move语义。不生成新的对象,直接将右值的资源转移走。

std::move()

到这,我们再来说这个函数是干啥的。注意我们上边说的是move语义,是语义层面的东西,和函数什么的一毛钱关系也没有。move语义一般都是通过move constructor实现的,这个move constructor的参数是一个右值引用。

好,说到这,你也可能会想,既然move语义是由move constructors实现的,要这个std::move有什么用。我们刚才说了,这个move constructors的参数是T&&,表示参数必须给一个右值才可以。可是我们实际情况中都是左值比较多,比如下面的例子:

#include "A.h"

{
...
A a;
A b;
...
a = b; // 此时,由于都是左值,一定调用的都是copy constructors
...
}

分析上面代码,我们知道,调用处是左值调用,对于资源有消耗。我们怎么实现move sementics呢?

#include "A.h"

{
...
A a;
A b;
...
a = std::move(b); // 将左值强制转化为右值引用,从而触发move constructor的调用,实现move semantics
...
}

所以,std::move的作用就是把一个左值强制转化为一个右值引用。主要是为了实现move semantics.

//The function returns the same as:

static_cast<remove_reference<decltype(arg)>::type&&>(arg)

参看如下代码:

// move example
#include <utility>      // std::move
#include <iostream>     // std::cout
#include <vector>       // std::vector
#include <string>       // std::string

int main () {
  std::string foo = "foo-string";
  std::string bar = "bar-string";
  std::vector<std::string> myvector;

  myvector.push_back (foo);                    // copies
  myvector.push_back (std::move(bar));         // moves

  std::cout << "myvector contains:";
  for (std::string& x:myvector) std::cout << ' ' << x;
  std::cout << '\n';

  return 0;
}
// myvector contains: foo-string bar-string

The first call to myvector.push_back copies the value of foo into the vector (foo keeps the value it had before the call).
The second call moves the value of bar into the vector. This transfers its content into the vector (while bar loses its value, and now is in a valid but unspecified state).

从上面的代码我们不难看出,如果左值被强制转化成右值,表明,这个右值的内容是可以被transfer的。那么,也就表明了这个右值引用所关联的左值,在之后必须是用不到,才能这么做!因为,这个右值引用关联的左值被掏空了!

#include <utility> // for std::move
#include <vector>
#include <string>
#include <iostream>

int main( void ){

    std::string foo = "foo-string";
    std::string bar = "bar-string";

    std::vector< std::string > myvector;

    myvector.push_back( std::move( foo ) );
    myvector.push_back( std::move( bar ) );

    std::cout << "myvector contains : " << std::endl;
    int sz = myvector.size();
    for( int i = 0; i < sz; ++i ){
        std::cout << myvector[i] << std::endl;
    }

    std::cout << foo << std::endl;
    std::cout << bar << std::endl;

    return 0;
}
/*
myvector contains : 
foo-string
bar-string


*/

输出发现,下面两行全部是空!也就是说之前被强制转换为右值的左值被掏空了!应该在确定,这个左值不在被使用的情况下才能这么做,因为之前的右值本生也是临时的,也就表明之后是不能用的。

所以,我再次强调,一个左值如果要对它进行std::move(),必须确定它之后都不在使用。或者给它赋予新的值才可以!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值