美文网首页
右值引用

右值引用

作者: book_02 | 来源:发表于2019-11-01 21:03 被阅读0次

右值引用是个效率工具,减少拷贝。
C++11标准提出的一类数据类型。用于实现转移语义(move semantic)与完美转发(perfect forwarding)

1. 几个定义

int i   = 5;                // i为左值, 5为右值
int &j  = i;                // j为左值引用
int &&k = std::move(i+5);   // k为右值引用

引用(reference)是指绑定到内存中的相应对象上。左值引用是绑定到左值对象上;右值引用是绑定到临时对象上。

设X为任何一种数据类型,则定义X&&是到数据类型X的右值引用(rvalue reference to X)。
传统的引用类型X&被称为左值引用(lvalue reference to X)。

右值引用与左值引用的变量都不能悬空(dangling),也即定义时必须初始化从而绑定到一个对象上。

2. 左值和右值

  1. 左值是对应(refer to)内存中有确定存储地址的对象的表达式的值,而右值是所有不是左值的表达式的值。
  2. 右值可以是字面量、临时对象等表达式。
  3. 左值与右值的根本区别在于是否允许取地址&运算符获得对应的内存地址。

3. 转移语义与std::move

C++在用临时对象或函数返回值给左值对象赋值时会产生拷贝,效率低。
如果把临时对象的内容直接移动(move)给被赋值的左值对象,效率改善将是显著的。这就是移动语义的来源。

所以转移语义与拷贝语义相对,类似电脑上文件的剪切与复制。

区分转移语义与拷贝语义用右值引用,有右值引用就是转移语义。

标准库函数std::move可以把左值或临终值,转化为右值引用。

有一点值得注意的时具名的右值引用被当作左值。

例子如下:

#include <iostream>
#include <vector>
#include <utility>

void process_value(int& i) {
    std::cout << "LValue processed: " << i << std::endl;
}

void process_value(int&& i) {
    std::cout << "RValue processed: " << i << std::endl;
}

int main()
{
    int i = 5;    
    int &j = i;   
    int &&k = std::move(i+5);

    process_value(j);               // LValue
    process_value(i + 5);           // RValue
    process_value(std::move(i));    // RValue. std::move把左值转化为右值引用
    process_value(k);               // LValue. 具名的右值引用被当作左值

    return 0;
}

结果:

LValue processed: 5
RValue processed: 10
RValue processed: 5
LValue processed: 10

上面只是简单的例子,用到类里面,与传统的拷贝赋值运算符(copy assignment)成员函数、拷贝构造(copy ctor)成员函数对应,
移动语义需要有移动赋值(move assignment)成员函数、移动构造(move ctor)成员函数的实现机制。
可以通过函数重载来确定是调用拷贝语义还是移动语义的实现。

上面的例子似乎只是说明了转移语义和std::move的用法,并没有看到转移语义带来的效率提升。
下面列举一个效率提升的例子:

#include <iostream>
#include <vector>
#include <utility>
#include <string>

int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;

    v.push_back(str);               // copy. use push_back(const T&) 
    std::cout << "After copy, str is \"" << str << "\"\n";

    v.push_back(std::move(str));    // move. use push_back(T&&)

    std::cout << "After move, str is \"" << str << "\"\n";

    std::cout << "The contents of the vector are : " << std::endl
        << v[0] << std::endl
        << v[1] << std::endl;

    return 0;
}

结果:

After copy, str is "Hello"
After move, str is ""
The contents of the vector are :
Hello
Hello

第1种使用常量左值引用形式的push_back,第2种使用右值引用形式的push_back
第1种用法,涉及到string的拷贝,且对原str不产生影响。
第2种用法,使用了转移语义,直接把原来的内容转移到vector里去,被转移的str对象处在一个“合法而未指定”的状态,所以为""。这样就不用涉及string的拷贝,所以提高了效率。

4. 完美转发与std::forward

前面提到,具名的右值引用会被当作左值。
下面的fn()的参数可以是右值引用,fn()里调用了子函数overloaded(), fn()把右值引用参数转发给子函数overloaded()
如果没有完美转发,由于具名的右值引用会被当作左值,子函数overloaded()接收到的参数始终是左值。
本可以接收右值引用参数的overloaded()在这时没有提高传参效率。
std::forward是用来做完美转发的函数,对原来的参数做原样转发。

#include <iostream>
#include <vector>
#include <utility>
#include <string>

// function with lvalue and rvalue reference overloads:
void overloaded(const int& x)   { std::cout << "[lvalue]"; }
void overloaded(int&& x)        { std::cout << "[rvalue]"; }

// function template taking rvalue reference to deduced type:
template <class T> 
void fn(T&& x) {
    overloaded(x);                   // always an lvalue
    overloaded(std::forward<T>(x));  // rvalue if argument is rvalue
}

int main()
{
    int a;

    std::cout << "calling fn with lvalue: ";
    fn(a);
    std::cout << '\n';

    std::cout << "calling fn with rvalue: ";
    fn(0);
    std::cout << '\n';

    return 0;
}

结果:

calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]

overloaded()可以接收左值引用参数,也可以接收右值引用参数。
第1次,fn()的参数是左值引用,overloaded(x);调用的是左值引用的函数,overloaded(std::forward<T>(x))调用的也是左值引用的函数。
第2次,fn()的参数是右值引用,overloaded(x);调用的还是左值引用的函数,overloaded(std::forward<T>(x))调用的则是与fn()的参数一样的右值引用的函数。参数得到了完美转发。

为什么要把fn()写成模板函数?
因为加了模板,fn()就也可以使用左值去调用了,去掉template <class T>,fn()只接受右值引用的参数。
这里面涉及到引用折叠原则:

  1. T& & (引用的引用) 被转化成 T&
  2. T&& & (rvalue的引用)被传化成 T&
  3. T& && (引用作rvalue) 被转化成 T&
  4. T&& && 被转化成 T&&

所以左值的参数传过来T&& &被转化为T&,右值参数传过来T&& &&被转化成 T&&,所以这样写之后,fn()就既可以接收左值引用参数,也可以接收右值引用参数了。

5. 参考

https://zh.wikipedia.org/wiki/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8
https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/index.html
https://zh.cppreference.com/w/cpp/utility/move
http://www.cplusplus.com/reference/utility/forward/

相关文章

网友评论

      本文标题:右值引用

      本文链接:https://www.haomeiwen.com/subject/rrvqbctx.html