C++的std::move与std::forward

一、什么是左值与右值

左值是指在内存中有确定地址的对象,它可以在赋值语句的左边使用。左值的特点包括:

  1. 左值可以出现在赋值符号=的左边;
  2. 可以获取左值的地址;
  3. 左值是持久存在的,生命周期可以跨越多个表达式。

例如,下面的代码中,a 和 c 都是左值:

int a = 1;
int b = 2;
int c = a + b;

右值则是指在内存中没有确定地址的临时数据,它只能在表达式的右边使用。右值的特点包括:

  1. 右值不能被取地址;
  2. 右值通常只是临时存在的,生命周期只能在当前表达式中存在;
  3. 右值可以是一个变量、常量、字面量或者表达式,例如5、2.5、函数的返回值等。

例如,下面的代码中,2 和 (a + b) 都是右值:

int a = 1;
int b = 2;
int c = 2; // 2是一个右值
int d = a + b; // (a + b)是一个右值

对于大多数类型的对象,它们可以用作左值和右值。例如,一个整数变量既可以在赋值语句的左边使用,也可以在表达式的右边使用。然而,一些特殊类型只能作为左值或者右值。例如:

  1. 纯右值引用(从 C++11 开始):只能作为右值使用的引用类型;
  2. constexpr 变量(从 C++11 开始):只能用作右值;
  3. 关键字 this:只能用作左值。

  

更深一层,可以将 L-value 的 L, 理解成 Location,表示定位,地址。将 R-value 的 R 理解成 Read,表示读取数据。

二、左值引用与右值引用

左值引用:其实就是绑定到左值的引用,通过&来获得左值引用。

左值引用的基本语法:

type &引用名 = 左值表达式;

请根据以上概念仔细领悟以下实例.

    int a = 3;
    const int b = 5;
    a = b + 2; // a是左值,b + 2是右值
    b = a + 2; // 错!b是只读的左值,无写入权,不能出现在赋值符号左边
    (a = 4) += 28; // a = 4是左值表达式,28是右值,+= 为赋值操作符
    34 = a + 2; // 错!34是字面量不能做左值
    int &r = a;   // 正确,r引用a

右值引用:为了支持移动操作,C++11引入了一种新的引用类型—右值引用。所谓右值引用就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用。如我们将要看到的,右值引用有一个重要的性质——只能绑定到一个将要销毁的对象。 因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。

右值引用的基本语法: 

type &&引用名 = 右值表达式;

右值引用的“&&”中间不可以有空格。请根据以上概念仔细领悟以下实例

    int i = 42;
    int &r = i;   // 正确,r引用i
    int &&rr = i   // 错误,不能将一个右值引用绑定到一个左值上
    int &r2 = i * 42;  // 错误,i*42是一个右值
    const int &r3 = i * 42;  //正确,我们可以将一个const的引用绑定到一个右值上
    int &&r2 = i * 42; //正确,将r2绑定到乘法结果上

 三、标准库std::move函数

虽然不能将一个右值引用直接绑定到一个左值上,但可以显式地将一个左值转换为对应的右值引用类型。我们通过调用一个名为std::move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件utility中。

    int rr1 = 10;
    int &&rr2 = rr1; // 错误
    int &&rr3 = std::move(rr1);  // OK, 将左值绑定到右值引用上

std::move调用告诉编译器:我们有一个左值,但我们希望像右值一样处理它。我们必须认识到,调用move就意味着承诺:除了对rr1赋值或者销毁之外,我们将不再使用它。在调用move之后,我们不能对移后源对象的值做任何修改。

注意:我们可以销毁一个移后源对象,也可以赋予它新值,但是不能使用一个移后源对象的值。

实例

#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;
    //调用常规的拷贝构造函数,新建字符数组,拷贝数据
    v.push_back(str);
    std::cout << "After copy, str is \"" << str << "\"\n";
    //调用移动构造函数,掏空str,掏空后,最好不要使用str
    v.push_back(std::move(str));
    std::cout << "After move, str is \"" << str << "\"\n";
    std::cout << "The contents of the vector are \"" << v[0]
                                         << "\", \"" << v[1] << "\"\n";
}

std::move 的函数原型定义

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);
}

四、标准库std::forward函数

std::forward通常是用于完美转发的,它会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值

一个经典的完美转发的场景是:

template <class... Args>
void forward(Args&&... args) {
    f(std::forward<Args>(args)...);
}

需要注意输入参数的类型是Args&&... , &&的作用是引用折叠,其规则是:

&& && -> &&
& && -> &
& & -> &
&& & -> &

由于我们需要保留输入参数的右值属性,因此Args后面需要跟上&&;2、std::forward的模板参数必须是<Args>,而不能是<Args...>,这是由于我们不能对Args进行解包之后传递给std::forward,而解包的过程必须在调用std::forward之后.

std::forward 的函数原型定义

template<class T>
constexpr T&& forward(std::remove_reference_t<T>& arg) noexcept{
    // forward an lvalue as either an lvalue or an rvalue
    return (static_cast<T&&>(arg));
}

template<class T>
constexpr T&& forward(std::remove_reference_t<T>&& arg) noexcept{
    // forward an rvalue as an rvalue
    return (static_cast<T&&>(arg));
}

std::remove_reference_t是一个模板类的类型别名,用于去掉T的引用属性(但不会去掉const属性,const属性可以用std::remove_const_t来去掉);如果forward接受的参数为左值的话,它将其转化成右值返回,这样既可以传给左值,又可以传给右值;如果传递的参数是个右值的话,它将其保留右值属性返回,这样只可以返回给右值。

五、remove_reference

remove_reference 是一个常用的类型转换工具模板,可以用于去除引用类型的引用修饰符

对于remove_reference是通过类模板的部分特例化进行实现的,其实现如下

// 普通版本
template <typename T>
struct remove_reference
{
    typedef T type;
};

//部分版本特例化,将用于左值引用
template <typename T>
struct remove_reference<T&>
{
    typedef T type;
};


//部分版本特例化,将用右值引用
template <typename T>
struct remove_reference<T&&>
{
    typedef T type;
};

  
//举例如下,下列定义的a、b、c三个变量都是int类型
int i;
remove_refrence<decltype(42)>::type a;             //使用原版本,
remove_refrence<decltype(i)>::type  b;             //左值引用特例版本
remove_refrence<decltype(std::move(i))>::type  b;  //右值引用特例版本 

参考:

C++:浅谈右值引用_拥抱@的博客-CSDN博客_函数可以将一个变量转换为右值引用类型

浅谈std::forward - 知乎

c++ 之 std::move 原理实现与用法总结_ppipp1109的博客-CSDN博客_c++ std::move

左值、左值引用、右值、右值引用_ppipp1109的博客-CSDN博客_右值引用赋值给左值

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值