“先传递值,然后移动”构造是一个糟糕的习惯用法吗? 由于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