• c++ std::move 和 std::forward


    • 在C++11标准之前,C++中默认的传值类型均为Copy语义,即:不论是指针类型还是值类型,都将会在进行函数调用时被完整的复制一份。所以引入了move和forward
    临时值(右值)简述
    func("some temporary string"); // 尽管直接将一个常量传入函数中, C++还是大概率会创建一个string的复制
    v.push_back(X()); // 初始化了一个临时X, 然后被复制进了vector
    a = b + c; // b+c是一个临时值, 然后被赋值给了a
    x++; // x++操作也有临时变量的产生(++x则不会产生)
    a = b + c + d; //c+d是一个临时变量, b+(c+d)是另一个临时变量
    
    vector<string> str_split(const string& s) {
      vector<string> v;
      // ...
      return v; // v是左值,但优先移动,不支持移动时仍可复制
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    使用 move
    // Copy constructor
    MyString(const MyString &str) {}
    // Move constructor
    MyString(MyString &&str) noexcept {}
    // Copy assignment
    MyString& operator=(const MyString& str) {}
    // Move assignment
    MyString& operator=(MyString&& str) {}
    
    //使用std::move
    void f_move(Object &&obj) {}
    Object(Object &&object) noexcept: _str(std::move(object._str)) {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 实际上,C++中的move函数只是做了类型转换,并不会真正的实现值的移动,因此对于自定义的类来说,如果要实现真正意义上的 “移动”,还是要手动重载移动构造函数和移动复制函数。即:我们需要在自己的类中实现移动语义,避免深拷贝,充分利用右值引用和std::move的语言特性。
    2. 通常情况下C++编译器会默认在用户自定义的class和struct中生成移动语义函数。但前提是:用户没有主动定义该类的拷贝构造等函数!
    3. 如果我们没有提供移动构造函数,只提供了拷贝构造函数,std::move()会失效但是不会发生错误,因为编译器找不到移动构造函数就去寻找拷贝构造函数,这也是拷贝构造函数的参数是const T&常量左值引用的原因
    4. c++11中的所有容器都实现了move语义,move只是转移了资源的控制权,本质上是将左值强制转化为右值使用,以用于移动拷贝或赋值,避免对含有资源的对象发生无谓的拷贝
    5. move对于拥有如内存、文件句柄等资源的成员的对象有效,如果是一些基本类型,如int和char[10]数组等,如果使用move,仍会发生拷贝(因为没有对应的移动构造函数),所以说move对含有资源的对象说更有意义。
    foward 向前的,前进的;

    完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另 一个函数,即传入转发函数的是左值对象,目标函数就能获得左值对象,转发函数是右值对象, 目标函数就能获得右值对象,而不产生额外的开销。

    1. 什么是foward?

      1. 问题:使用move后,处理临时变量用右值引用T&&,处理普通变量用const引用const T&,需要分别建立两个函数,然后入参使用不同的类型,每个函数都要写两遍。
      2. 能不能将T &&类型和const T &类型合二为一呢?
    2. std::forward也被称为完美转发,即:保持原来的值属性不变:

      1. 如果原来的值是左值,经std::forward处理后该值还是左值。如果外面传来了左值,它就转发左值并且启用copy,同时它也还能保留const。
      2. 如果原来的值是右值,经std::forward处理后它还是右值。如果外面传来了右值临时变量,它就转发右值并且启用move语义。

    这样一来,我们就可以使用forward函数对入参进行封装,从而保证了入参的统一性,从而可以实现一个方法处理两种类型!
    正因为如此,forward函数被大量用在了入参值类型情况不确定的C++模板中!

    template<typename T>
    void f_forward(T &&t) {
     
     
        Object a = std::forward<T>(t);//调用了std::forward(t)来创建一个新的对象。
     
     
        std::cout << "forward this object, address: " << &a << std::endl;
    }
     
     
    int main() {
        Object obj{"abc"};
    
        //分别使用一个左值和一个右值调用了该模板函数。
        f_forward(obj);
        f_forward(Object("def"));
        return 0;
    }
    
    
    build this object, address: 000000CFAE8FFC78
    copy this object, address: 000000CFAE8FFBD8
    forward this object, address: 000000CFAE8FFBD8
    destruct this object, address: 000000CFAE8FFBD8
    build this object, address: 000000CFAE8FFCB8
    move this object!
    forward this object, address: 000000CFAE8FFBD8
    destruct this object, address: 000000CFAE8FFBD8
    destruct this object, address: 000000CFAE8FFCB8
    destruct this object, address: 000000CFAE8FFC78
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    move和forward函数的区别
    1. 基本上forward可以cover所有的需要move的场景,毕竟forward函数左右值通吃
    2. 那为什么还要使用move呢?原因主要有两点:
      1. 首先,forward函数常用于模板函数这种入参情况不确定的场景中,在使用的时候必须要多带一个模板参数forward,代码略复杂
      2. 此外,明确只需要move临时值的情况下如果使用了forward,会导致代码意图不清晰,其他人看着理解起来比较费劲
  • 相关阅读:
    Qt正则表达式
    消息队列 - RabbitMQ
    【SpingBoot拦截器】实现两个接口,配置拦截路径
    openstack——2、搭建虚拟机(配置认证服务、镜像服务)
    Jmeter是用来做什么的?
    STL再回顾(非常见知识点)
    2021-09-24有史以来最小的人造飞行结构:有翅膀的微芯片
    JVM的类的生命周期
    Layui 主窗口调用 iframe 弹出框模块,获取控件的相应值
    医保医用耗材编码目录——在线查询
  • 原文地址:https://blog.csdn.net/qq_17338093/article/details/133526673