zoukankan      html  css  js  c++  java
  • C++ move和forward

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

    两个C++的基础背景

    1. C++传值默认是copy
    2. copy开销很大

    C++11前的状况: 没法避免临时变量的copy

    基于以上背景, C++11以前是没法避免copy临时变量的, 如下面例子, 他们都要经历至少一次复制操作:

    func("some temporary string"); //初始化string, 传入函数, 可能会导致string的复制
    
    v.push_back(X()); //初始化了一个临时X, 然后被复制进了vector
    
    a = b + c; //b+c是一个临时值, 然后被赋值给了a
    
    x++; //x++操作也有临时变量的产生
    
    a = b + c + d; //c+d一个临时变量, b+(c+d)另一个临时变量

    (这些临时变量在C++11里被定义为rvalue, 右值, 因为没有对应的变量名存他们)

    (同时有对应变量名的被称为lvalue, 左值)

    案例:

    以上copy操作有没有必要呢? 有些地方可不可以省略呢? 我们来看看下面一个案例, 之后我们会用到它来推导出为什么我们需要move和forward:

    假如有一个class A, 带有一个set函数, 可以传两个参数赋值class里的成员变量:

    class A{...};
    
    void A::set(const string & var1, const string & var2){
      m_var1 = var1;  //copy
      m_var2 = var2;  //copy
    }

    下面这个写法是没法避免copy的, 因为怎么着都得把外部初始的string传进set函数, 再复制给成员变量:

    A a1;
    string var1("string1");
    string var2("string2");
    a1.set(var1, var2); // OK to copy

    但下面这个呢? 临时生成了2个string, 传进set函数里, 复制给成员变量, 然后这两个临时string再被回收. 是不是有点多余?

    A a1;
    a1.set("temporary str1","temporary str2"); //temporary, unnecessary copy

    上面复制的行为, 在底层的操作很可能是这样的:

    (1)临时变量的内容先被复制一遍

    (2)被复制的内容覆盖到成员变量指向的内存

    (3)临时变量用完了再被回收

    这里能不能优化一下呢? 临时变量反正都要被回收, 如果能直接把临时变量的内容, 和成员变量内容交换一下, 就能避免复制了? 如下:

    (1)成员变量内部的指针指向"temporary str1"所在的内存

    (2)临时变量内部的指针指向成员变量以前所指向的内存

    (3)最后临时变量指向的那块内存再被回收

    上面这个操作避免了一次copy的发生, 其实它就是所谓的move语义.

    ref: 

    C++11: 引入rvalue, lvalue和move

    那么这个临时变量, 在以前是解决不了了. 为了填这个坑, 蛋疼的C++委员会就说, 不如把C++搞得更复杂一些吧!

    于是就引入了rvalue和lvalue的概念, 之前说的那些临时变量就是rvalue. 上面说的避免copy的操作就是std::move

    再回到我们的例子:

    没法避免copy操作的时候, 还是要用const T %把变量传进set函数里, 现在T &叫lvalue reference(左值引用)了, 如下:

    void set(const string & var1, const string & var2){
      m_var1 = var1;  //copy
      m_var2 = var2;  //copy
    }
    A a1;
    string var1("string1");
    string var2("string2");
    a1.set(var1, var2); // OK to copy

    传临时变量的时候, 可以传T &&, 叫rvalue reference(右值引用), 它能接收rvalue(临时变量), 之后再调用std::move就避免copy了.

    void set(string && var1, string && var2){
      //avoid unnecessary copy!
      m_var1 = std::move(var1);  
      m_var2 = std::move(var2);
    }
    A a1;
    //temporary, move! no copy!
    a1.set("temporary str1","temporary str2");

    新的问题: 避免重复

    现在终于能处理临时变量了, 但如果按上面那样写, 处理临时变量用右值引用string &&, 处理普通变量用const引用const string &...

    这代码量有点大呀? 每次都至少要写两遍, overload一个新的method吗?

    回忆一下程序员的核心价值观是什么? 避免重复!

    perfect forward (完美转发)

    上面说的各种情况, 包括传const T &T &&, 都可以由以下操作代替:

    template<typename T1, typename T2>
    void set(T1 && var1, T2 && var2){
      m_var1 = std::forward<T1>(var1);
      m_var2 = std::forward<T2>(var2);
    }
    
    //when var1 is an rvalue, std::forward<T1> equals to static_cast<[const] T1 &&>(var1)
    //when var1 is an lvalue, std::forward<T1> equals to static_cast<[const] T1 &>(var1)

    forward能转发下面所有的情况:

    [const] T &[&]

    也就是:

    const T &
    T &
    const T &&
    T &&

    那么forward就是上面一系列操作的集大成者.

    如果外面传来了rvalue临时变量, 它就转发rvalue并且启用move语义.

    如果外面传来了lvalue, 它就转发lvalue并且启用复制. 然后它也还能保留const.

    这样就能完美转发(perfect forwarding)所有情况了.

    那我们有了forward为什么还要用move?

    技术上来说, forward确实可以替代所有的move.

    可能会有人在这里和我杠吧. 这你得去和"Effective Modern C ++"的作者Scott Meyers去争了:

    "From a purely technical perspective, the answer is yes: std::forward can do it all. std::move isn’t necessary. "
    来源:

    但还有一些问题:

    首先, forward常用于template函数中, 使用的时候必须要多带一个template参数T: forward<T>, 代码略复杂;

    还有, 明确只需要move的情况而用forward, 代码意图不清晰, 其他人看着理解起来比较费劲.

    更技术上来说, 他们都可以被static_cast替代. 为什么不用static_cast呢? 也就是为了读着方便易懂.

    ref: 

    总结

    到这里, move和forward为什么会出现, 有什么用就彻底搞明白了. 其实也就是引入了好几个复杂概念, 来填临时变量的一个坑.

  • 相关阅读:
    IntelliJ IDEA 自定义 Generate POJOs.groovy 生成 POJO
    MyBatisPlus入门
    Angular中内置模块和自定义模块
    Angular跳转传值(get,动态路由,js)
    Angular路由配置
    Angular数据请求(get,post)
    Angular父子组件之间通讯传值
    Angular双向数据绑定
    Angular项目分析
    Angular环境搭建
  • 原文地址:https://www.cnblogs.com/linxisuo/p/14435185.html
Copyright © 2011-2022 走看看