【问题标题】:What is the correct way to use move semantics in C++?在 C++ 中使用移动语义的正确方法是什么?
【发布时间】:2019-02-20 13:08:14
【问题描述】:

考虑这段代码:

class A {
private:
    std::string data;
public:
    void set_data(std::string&& data) {
        this->data = std::move(data); // line 6
    }
};

int main() {
    std::string move_me = "Some string";
    A a;
    a.set_data(std::move(move_me)); // line 13
}

我知道我们需要在第 13 行调用 std::move() 以便它将左值转换为右值引用(这听起来正确吗?我是新手)。

但是,在第 6 行,我们需要再次使用std::move() 吗?我假设不会,因为我们已经传递了一个右值引用,并且将调用std::string 的移动构造函数。对吗?

【问题讨论】:

  • 左值本质上是一个命名变量,因此第 6 行的data 是一个左值,必须重铸。
  • @OZ17 在没有转发引用的情况下使用forward 没有多大意义。
  • @OZ17 std::forwardstd::move 不一样。您应该只使用 std::forward 作为 template cv-unqualified rvalue reference 传递的内容。 set_data方法中没有template参与
  • @bobl 是的,没错! This talk Scott Meyers (链接应该带有时间戳,但它已损坏,它是 16 分 44 秒)很好地解释了它。问题是T&& 不一定总是意味着右值引用(这是一个谎言,但这是一个舒服的谎言)。有时,由于引用折叠,它成为左值引用,std::forward '检测到它`并进一步转发正确的类型(左值或右值引用)
  • 那我很对不起你。这意味着您和您的同事不知道std::forward 确实并且更喜欢编写表达与预期不同的东西的代码。我建议您从我上面链接 3 cmets 的演讲中了解更多关于 std::movestd::forward 之间的区别

标签: c++ stl move move-semantics


【解决方案1】:

但是,在第 6 行,我们需要再次使用std::move() 吗?

是的。为什么?因为在set_data 内部,data(参数)是一个左值,因为它有一个名字。两个std::moves 都是在a 中将move_me 实际移动到data 所必需的。

如果没有6 上的std::movemove_me 将不会被移动,因为这将调用std::string(const std::string&),而不是std::string(std::string&&)

记住 - 如果某物有名字,它就是一个左值。

【讨论】:

    【解决方案2】:

    似乎两个答案都是正确的,我只是在标准中添加了一段解释为什么在#6#13 行中使用std::move() 是正确的,以及为什么它是左值,即使类型是右值在#6 行中。

    表达式的类型就是标识符的类型。结果是标识符表示的实体。如果实体是函数、变量或数据成员,则结果为左值,否则为纯右值。 5.1.1[expr.prim.general]/8

    因此,应用标准中的这条规则,我们有望直接得到答案。

    左值

        // move_me is identifier of a variable denotes to itself the result is lvalue
        std::string move_me = "Some string";
    

    右值

       // constructing temporary e.g no  identifier is an rvalue
       std::string("Some string") ; 
    

    左值

      // the variable data has type rvalue reference to move_ms, it denotes entity move_ms
      // the result is lvalue
      void set_data(std::string&& data);
    

    左值

    // the variable data has type  lvalue reference to move_ms, 
    //it denotes entity move_ms the result is lvalue
    void set_data(std::string& data);
    

    左值或右值 - 通用引用

    //the variable data has type universal reference it either holds lvalue or rvalue
    template<typename T> void setdata(T && data) ;
    

    所以,右值引用不是右值,事情可能会出错

    Base(Base const & rhs); // non-move semantics
    Base(Base&& rhs); // move semantics 
    

    如果你错过了使用 std::move()

     Derived(Derived&& rhs) : Base(rhs) // wrong: rhs is an lvalue
     {
      // Derived-specific stuff
     }
    

    正确的版本是:

      Derived(Derived&& rhs) : Base(std::move(rhs)) // good, calls Base(Base&& rhs)
      {
      // Derived-specific stuff
      }
    

    还有

    • 创建对左值的左值引用 - 确定
    • 创建对右值的右值引用 - 好的
    • 为右值创建左值 const 引用 - 确定
    • 创建对右值的左值引用 - 编译错误

    【讨论】:

      【解决方案3】:

      #6#13 线上都需要它。

      有来自 Scott Mayers 的 nice post 关于这个主题。

      最能接受的方式是

      // 1: full flexibility for the caller to decide where the data state comes from
      struct X
      {
          Y data_;
          explicit X(const Y& data) : data_(data) { }
          explicit X(Y&& data) : data_(std::move(data)) { }
      };
      
      // 2: forced copy or move at the call site and zero-copy move into the internal state of the X
      struct X
      {
          Y data_;
          explicit X(Y data) : data_(std::move(data)) { }
      };
      
      // 3: same as the setter below, but can have quite different forms based on what exactly is required
      struct X
      {
          Y data_;
          template <class... Z>
          explicit X(Z&&... arg) : data_(std::forward<Z>(args)...) { }
      }
      

      setter 最好以“透明”样式完成,有效地委托给字段的赋值运算符。

      template <typename Arg> void setData(Arg&& arg) {
          data_ = std::forward<Arg>(arg);
      }
      

      我建议编写一个简单的类,其中包含各种复制/移动构造函数/操作符,并带有调试打印,并稍微玩一下这样的类,以培养如何使用 &amp;&amp;std::forwardstd::move。无论如何,这就是我过去所做的。

      【讨论】:

      • 这没有回答问题
      • 但是,如果我特别想要一个 setter 方法以及您展示的移动构造函数怎么办?
      • 我认为有 5 个人的想法和我一样。
      • @appleapple - 当然,伙计,如果它适合你
      • @bobl - template &lt;typename T&gt; setData(T&amp;&amp; data) { _data = std::forward&lt;T&gt;(data); } 是将移动决策委托给 _data 的赋值运算符的正确语法。
      猜你喜欢
      • 1970-01-01
      • 2017-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 2012-05-09
      • 1970-01-01
      相关资源
      最近更新 更多