Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/135.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
“先传递值,然后移动”构造是一个糟糕的习惯用法吗? 由于C++中有移动语义学,现在通常做< /P> void set_a(A a) { _a = std::move(a); }_C++_C++11_Move Semantics_Pass By Value_Rvalue Reference - Fatal编程技术网

“先传递值,然后移动”构造是一个糟糕的习惯用法吗? 由于C++中有移动语义学,现在通常做< /P> void set_a(A a) { _a = std::move(a); }

“先传递值,然后移动”构造是一个糟糕的习惯用法吗? 由于C++中有移动语义学,现在通常做< /P> void set_a(A a) { _a = std::move(a); },c++,c++11,move-semantics,pass-by-value,rvalue-reference,C++,C++11,Move Semantics,Pass By Value,Rvalue Reference,理由是,如果a是右值,则该副本将被删除,只需移动一步 但是如果a是左值,会发生什么呢?似乎会有一个复制构造,然后是一个移动分配(假设a有一个合适的移动分配操作符)。如果对象的成员变量太多,则移动指定的成本可能会很高 另一方面,如果我们这样做 void set_a(const A& a) { _a = a; } 只有一份复印作业。如果我们将通过LValk,我们能说这种方法比通行值的习惯更好吗?< /P> < P>昂贵的移动类型在现代C++用法中是罕见的。如果您担心移动的成本,请编写两个重

理由是,如果
a
是右值,则该副本将被删除,只需移动一步

但是如果
a
是左值,会发生什么呢?似乎会有一个复制构造,然后是一个移动分配(假设a有一个合适的移动分配操作符)。如果对象的成员变量太多,则移动指定的成本可能会很高

另一方面,如果我们这样做

void set_a(const A& a) { _a = a; }

只有一份复印作业。如果我们将通过LValk,我们能说这种方法比通行值的习惯更好吗?< /P> < P>昂贵的移动类型在现代C++用法中是罕见的。如果您担心移动的成本,请编写两个重载:

void set_a(const A& a) { _a = a; }
void set_a(A&& a) { _a = std::move(a); }
或者一个完美的转发设置器:

template <typename T>
void set_a(T&& a) { _a = std::forward<T>(a); }
模板
无效集{u a=std::forward(a);}
它将接受左值、右值和任何其他隐式转换为
decltype(_a)
,而不需要额外的拷贝或移动


尽管从左值进行设置时需要额外移动,但这种习惯用法并不坏,因为(a)绝大多数类型都提供固定时间移动,(b)复制和交换在一行代码中提供异常安全性和接近最佳的性能。

传递值,那么move实际上是一个很好的习语,用来形容那些你知道是可移动的对象

正如您所提到的,如果传递了一个右值,它将要么删除副本,要么被移动,然后在构造函数中移动它

您可以显式重载复制构造函数和移动构造函数,但是如果有多个参数,则会变得更复杂

举个例子

class Obj {
  public:

  Obj(std::vector<int> x, std::vector<int> y)
      : X(std::move(x)), Y(std::move(y)) {}

  private:

  /* Our internal data. */
  std::vector<int> X, Y;

};  // Obj
类Obj{
公众:
Obj(标准::向量x,标准::向量y)
:X(std::move(X)),Y(std::move(Y)){
私人:
/*我们的内部数据*/
std::向量X,Y;
};  // Obj
假设您想要提供显式版本,那么您将得到4个构造函数,如下所示:

class Obj {
  public:

  Obj(std::vector<int> &&x, std::vector<int> &&y)
      : X(std::move(x)), Y(std::move(y)) {}

  Obj(std::vector<int> &&x, const std::vector<int> &y)
      : X(std::move(x)), Y(y) {}

  Obj(const std::vector<int> &x, std::vector<int> &&y)
      : X(x), Y(std::move(y)) {}

  Obj(const std::vector<int> &x, const std::vector<int> &y)
      : X(x), Y(y) {}

  private:

  /* Our internal data. */
  std::vector<int> X, Y;

};  // Obj
类Obj{
公众:
Obj(标准::矢量和x,标准::矢量和y)
:X(std::move(X)),Y(std::move(Y)){
对象(标准::向量和x,常数标准::向量和y)
:X(std::move(X)),Y(Y){
对象(常量std::vector&x,std::vector&y)
:X(X),Y(std::move(Y)){
对象(常量标准::向量和x,常量标准::向量和y)
:X(X),Y(Y){}
私人:
/*我们的内部数据*/
std::向量X,Y;
};  // Obj
正如您所看到的,随着参数数量的增加,所需构造函数的数量以排列方式增加

如果您没有具体的类型,但有一个模板化的构造函数,则可以使用完美转发,如下所示:

class Obj {
  public:

  template <typename T, typename U>
  Obj(T &&x, U &&y)
      : X(std::forward<T>(x)), Y(std::forward<U>(y)) {}

  private:

  std::vector<int> X, Y;

};   // Obj
类Obj{
公众:
模板
Obj(T&x、U&y)
:X(std::forward(X)),Y(std::forward(Y)){}
私人:
std::向量X,Y;
};   // Obj
参考资料:

  • 对于存储值的一般情况,仅传递值是一个很好的折衷方案-

    对于只传递左值的情况(一些紧密耦合的代码),这是不合理的,不明智的

    如果怀疑两者都能提高速度,首先要三思而后行,如果没有帮助的话,就衡量一下

    在不存储值的地方,我更喜欢通过引用传递,因为这样可以防止无数不必要的复制操作

    最后,如果编程可以简化为不经思考的规则应用,我们可以把它留给机器人。所以我觉得把这么多精力放在规则上不是个好主意。对于不同的情况,最好关注优势和成本是什么。成本不仅包括速度,还包括代码大小和清晰度等。规则通常不能处理这种利益冲突

    但是如果
    a
    是左值,会发生什么呢?看来会有一份 然后是移动分配(假设a有正确的移动 赋值运算符)。如果对象已更改,则移动指定的成本可能会很高 成员变量太多

    问题已被很好地发现。我甚至不会说“先传递值,然后移动”构造是一个糟糕的习惯用法,但它肯定有潜在的缺陷

    如果您的类型移动成本很高,并且/或者移动它本质上只是一个副本,那么按值传递的方法是次优的。此类类型的示例包括具有固定大小数组作为成员的类型:移动可能相对昂贵,而移动只是一个副本。另见

    在这方面

    传递值方法的优点是,您只需要维护一个函数,但您需要为此付出性能代价。这取决于应用程序的维护优势是否大于性能损失

    <强>通过LValk和RValk引用方法可能会导致维护头痛,如果您有多个参数。< /强>考虑:

    #include <vector>
    using namespace std;
    
    struct A { vector<int> v; };
    struct B { vector<int> v; };
    
    struct C {
      A a;
      B b;
      C(const A&  a, const B&  b) : a(a), b(b) { }
      C(const A&  a,       B&& b) : a(a), b(move(b)) { }
      C(      A&& a, const B&  b) : a(move(a)), b(b) { }
      C(      A&& a,       B&& b) : a(move(a)), b(move(b)) { }  
    };
    
    #包括
    使用名称空间std;
    结构A{vector v;};
    结构B{vector v;};
    结构C{
    A A;
    B B;
    C(常数A&A,常数B&B):A(A),B(B){}
    C(const A&A,B&B):A(A),B(move(B)){}
    C(A&A,constb&B):A(move(A)),B(B){}
    C(A&&A,B&&B):A(move(A)),B(move(B)){}
    };
    
    如果您有多个参数,您将有一个排列问题。在这个非常简单的示例中,维护这4个构造函数可能还没有那么糟糕。但是,在这个简单的例子中,我会认真考虑使用单值函数

    的传递值方法。
    C(A,B):A(move(A)),B(move(B)){}

    而不是上述4个构造函数


    长话短说,这两种方法都没有缺点。根据实际的分析信息做出决策,而不是过早地优化。

    我回答自己,因为我将尝试总结一些答案。在每种情况下,我们有多少个移动/拷贝

    (A)通过val
    void foo1( A a ); // easy to read, but unless you see the implementation 
                      // you don't know for sure if a std::move() is used.
    
    void foo2( const A & a ); // longer declaration, but the interface shows
                              // that no copy is required on calling foo().
    
    A a;
    foo1( a );  // copy + move
    foo2( a );  // pass by reference + copy
    
    A a;
    foo1( a );  // caller copies, foo1 moves
    foo2( a );  // foo2 copies