浅谈std::forward与std::move

一、背景介绍

什么是左值和右值?

1、左值引用

  C/C++语言中可以放在赋值符号左边的变量,左值是代表一个内存地址值,并且通过这个内存地址,就可以对内存进行读并且写(主要是能写)操作。相对应的还有右值:当一个符号者常量放在操作符右边的时候,计算机就读取他们的“右值”,也就是其代表的真实值。       
       简单来说就是,左值相当于地址值,右值相当于数据值。右值指的是引用了一个存储在某个内存地址里的数据。
左值右值翻译:
       L-value中的L指的是Location,表示可寻址。Avalue (computer science)that has an address.
       R-value中的R指的是Read,表示可读。in computer science, a value that does not have an address in a computer language


左值引用的基本语法: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=i;   //正确,r引用i


2、右值引用

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

右值引用

        右值引用的基本语法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函数

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

int &&rr3 =std::move(rr1);  //OK

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是通过类模板的部分特例化进行实现的,其实现代码如下

//原始的,最通用的版本
template <typename T> struct remove_reference{
    typedef T type;  //定义T的类型别名为type
};
 
//部分版本特例化,将用于左值引用和右值引用
template <class T> struct remove_reference<T&> //左值引用
{ typedef T type; }
 
template <class 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;  //右值引用特例版本 

参考:

https://blog.csdn.net/tonglin12138/article/details/91479048

https://zhuanlan.zhihu.com/p/92486757

https://blog.csdn.net/p942005405/article/details/84644069/

https://blog.csdn.net/p942005405/article/details/84644101

猜你喜欢

转载自blog.csdn.net/sinat_31608641/article/details/107346493
今日推荐