【C++进阶笔记】右值引用和完美转发

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

右值引用

C++中的值类别(Value Category)

一般而言C++中将值分为两种类别:左值(lvalue)和右值(rvalue)。有一种细的分类方式将其分成了三类:lvaluexvalueprvalue。这里的xvalue指的是lvalue转的rvalue和rvalue转的lvalueprvalue是指纯右值逻辑上存在可能会被编译器优化的rvalue这个后面再详细说。先重点说lvalue和rvalue:

C++中的部分概念很难定义手册中也都是以示例的方式给出。下面的定义也是只是我自己的理解。

  • lvalue:有读写权限的内存地址
  • rvalue:只有可读权限的内存地址或者说真实存在但我们不可见的内存地址

lvalue的例子

std::string str("123");  // 可读可写
int i = 5;

rvalue的例子

字面值:42 true nullptr// 只能读
函数返回值:std::string fun();   // 如果不写成这样std::string str = fun(); 我们都看不见这个内存地址

但是如果我们想操作rvalue怎么办C++11新增的std::move可以解决这个问题。首先为什么我们非得操作rvalue因为我们想获取更多的性能上的好处。比如:

void fun(const std::string& str) {
    // something
}

std::string long_string();    // 返回一个很长的std::string

fun(long_string());

将long_string()的返回值传递给fun的时候会调用一次std::string的拷贝构造函数由于这个string很长性能开销很大。所以C++11就通过右值引用的方式来解决这个问题。

右值引用的例子

修改fun来支持右值引用

class Widget {
public:
void fun(std::string&& str) {
    p_str_.swap(str);
}
private:
    std::string p_str_;
}

std::string long_string();    // 返回一个很长的std::string

Widget widget;
widget.fun(long_string());

此时在fun中我们就可以直接操作long_string()返回的rvalue了比如swap操作效率就会高很多。

xvalue

lvalue和rvalue都可以转成xvaluexvalue是可以随意破坏内存地址的一种特殊lvalue。lvalue可以通过std::move表明这个内存地址可以随意操作。将rvalue传递给右值引用方法之后他就会变成xvalue因为只有这样我们才可以操作这个内存。

int i = 10;
std::move(i);   // rvalue

void fun(std::string&& str) {
    // 可以调用str.swap方法操作str
}

右值引用的问题

假如fun函数接收两个string类型的参数如下:

void fun(const std::string& str1, const std::string& str2);
void fun(const std::string& str1, std::string&& str2);
void fun(std::string&& str1, const std::string& str2);
void fun(std::string&& str1, std::string&& str2);

这样就可以支持各种情况的右值引用了但是如果是3个参数4个呢?排列组合难道要写8个方法16个方法?C++11引入了完美转发来解决这个问题。

完美转发

完美转发的标准写法:

std::string str;
foo(str);                     // fun收到string类型lvalue

template <typename T>
void foo(T&& value) {
    fun(std::forward<T>(value));
}

C++ 11标准为了更好地实现完美转发特意为类型推到指定了新的类型匹配规则又称为引用折叠规则(假设用 A 表示实际传递参数的类型):

  • 当实参为左值或者左值引用(A&)时函数模板中 T&& 将转变为 A&(A& && = A&)
  • 当实参为右值或者右值引用(A&&)时函数模板中 T&& 将转变为 A&&(A&& && = A&&)

这样fun和foo收到参数类别就一致了。

解决右值引用的问题

void fun1(const std::string& str);
void fun1(std::string&& str);
void fun2(const std::string& str);
void fun2(std::string&& str);

template <typename T1, typename T2>
void foo(T1&& v1, T2&& v2) {
    fun1(std::forward<T1>(v1));
    fun2(std::forward<T2>(v2));
}

上面代码有一个风险就是如果传递给foo的是同一个string可能就会有问题。如下测试代码:

#include <iostream>

void fun1(std::string& str) {
  std::cout << "fun1 &: " << str << std::endl;
  str = "fun1";
}

void fun1(std::string&& str) {
  std::cout << "fun1 &&: " << str << std::endl;
  std::string tmp;
  tmp.swap(str);
  str = "fun1 &&";
}

void fun2(std::string& str) {
  std::cout << "fun2 &: " << str << std::endl;
}

void fun2(std::string&& str) {
  std::cout << "fun2 &&: " << str << std::endl;
}

template <typename T1, typename T2>
void foo(T1&& v1, T2&& v2) {
    fun1(std::forward<T1>(v1));
    fun2(std::forward<T2>(v2));
}

int main() {
  std::string str = "123";
  foo(std::move(str), std::move(str));
  return 0;
}

输出结果

fun1 &&: 123
fun2 &&: fun1 &&

参考

C++11完美转发及实现方法详解

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++