【C++】左值与右值

2022-04-12  本文已影响0人  离原春草

日拱一卒,功不唐捐

日常工作中经常遇到右值引用的相关逻辑,或者想要通过右值引用来优化程序性能,不过因为对右值引用只有一知半解,因此常常需要查阅资料才能拼凑出只鳞片羽的局部印象,难以构建整体理解,这里将平时搜集到的一些资料与自己的一些理解汇总到一起,从而避免每次的重复搜索与整理。

这里由于不是(也没有时间)系统的去搜集所有的内容,因此输出的结论会显得凌乱与分散,寄希望于等到内容丰富到一定程度再进行梳理。

C++的值类别

value总的来说,可以分成glvalue跟rvalue两类,其中glvalue指的是泛左值(generalized lvalue),rvalue为右值。

泛左值包含左值lvalue与将亡值xvalue(expiring value,快要销毁的对象),而右值rvalue同样包含纯右值prvalue(pure rvalue)与将亡值xvalue。

这里就有人要问了,将亡值这个反骨崽到底是左值还是右值?我只能说视情况而定,根据需要,可以是左值,也可以是右值,举个例子:

MoveTest GetMoveTestInstance()
{
  MoveTest t;
  return t;
}

MoveTest k = GetMoveTestInstance();

上面这个构造函数,如果我们为MoveTest类定义了移动构造函数,那么GetMoveTestInstance中的将亡值t就是右值,否则就是左值。

具名的右值是左值
所谓具名的右值,指的是如果某个用&&表示的对象有了名字,那么这个对象就会被当成左值来使用,下面给个例子:

#include <iostream>
#include <vector>
#include <set>
#include <assert.h>
using namespace std;

class MoveTest
{
public:
    int i = 0;
    MoveTest(int data) : i( data )
    {
        cout << "Move Test Constructor" << endl;
     }

    MoveTest( const MoveTest& other ) :  i(other.i)
    {
        cout << "Move Test Copy Constructor" << endl;
    }

    MoveTest( MoveTest&& other ) : i( other.i )
    {
        cout << "Move Test Move Constructor" << endl;
    }

    MoveTest& operator=( const MoveTest& other )
    {
        if( this != &other )
        {
            i = other.i;
            cout << "Move Test Copy Assignment" << endl;
        }
        return *this;
    }

    MoveTest& operator=( MoveTest&& other )
    {
        if( this != &other )
        {
            i = other.i;
            cout << "Move Test Move Assignment" << endl;
        }
        return *this;
    }
};

void CheckCopy( MoveTest& Input )
{
    MoveTest Local = Input;
    cout << "CheckCopy:" << Local.i << endl;
}

void CheckMoveAssign( MoveTest&& Input )
{
    MoveTest Local( 0 );
    Local = Input;
    cout << "CheckMoveAssign:" << Local.i << endl;
}

void CheckMove( MoveTest&& Input )
{
    MoveTest Local = Input;
    cout << "CheckMove:" << Local.i << endl;
}

void Test()
{
    MoveTest Original( 3 );
    CheckCopy( Original );
    //CheckMove( Original ); // error: an rvalue cannot be bound to an lvalue
    CheckMove( move(Original) );
    CheckMoveAssign( MoveTest(2) );
}

int main()
{
    Test();
    return 0;
}

运行结果为:

Move Test Constructor
Move Test Copy Constructor
CheckCopy:3
Move Test Copy Constructor
CheckMove:3
Move Test Constructor
Move Test Constructor
Move Test Copy Assignment
CheckMoveAssign:2

首先,我们要想将一个左值赋值给一个右值的形参,需要调用std::move接口进行显式转换;
其次,在CheckMove函数中,MoveTest Local = Input;我们理解应该调用移动构造函数进行构造,但实际上调用了拷贝构造函数,这就是前面说的具名的右值会被看成是左值导致的,既然是左值,那么自然使用拷贝构造函数。

既然具名的右值是左值,那么这种右值有什么作用呢?注意,右值提出的主要目的是降低数据拷贝的时间与空间消耗,而即使被看成左值来使用,这个避免拷贝的特性还依然是正常使用的。

另一个问题是,如果我们想要对具名的右值调用移动构造函数要怎么办,通过传参数的方式真的没有办法做到了吗?当然,不是,C++为我们提供了将左值转换为右值使用的接口std::move,我们只需要将函数实现改为如下形式,就能够调用移动构造函数了:

void CheckMove( MoveTest&& Input )
{
    MoveTest Local = move(Input);
    cout << "CheckMove:" << Local.i << endl;
}

参考

[1]. c++移动构造函数

上一篇 下一篇

猜你喜欢

热点阅读