为什么要使用移动构造函数?
why use a move constructor?
我有点不明白你为什么要 use/need 移动构造函数。
如果我有以下内容:
vector fill(istream& is)
{
vector res;
for(double x; is >> x; res.push_back(x));
return res;
}
void bar()
{
vector vec = fill(cin);
// ... use vec ...
}
我可以通过添加 vector fill(istream& is, vector& res)
.
删除 return res 的需要,因此不调用复制构造函数
那么移动构造函数有什么意义呢?
假设你接下来将 std::vector<T>
放入 std::vector<std::vector<T>>
(如果你认为向量不应该嵌套,假设内部类型为 std::string
并假设我们正在讨论 std::string
的移动构造函数):即使你 可以 添加一个空对象并就地填充它,最终矢量将需要在调整大小时重新定位,此时移动元素派上用场。
请注意,从函数返回不是移动构造的主要动机,至少在效率方面不是:效率很重要构建代码以启用复制省略甚至通过避免移动来进一步提高性能。
尽管如此,移动构造函数在语义上可能仍然相关,因为返回要求类型是可复制的或可移动的。某些类型(例如流)不可复制,但它们是可移动的并且可以通过这种方式返回。
在您的示例中,编译器可能会应用 RVO - Return 值优化,这意味着您的函数将被内联,因此不会发生 return - 并且不会应用任何移动语义。仅当它不能应用 RVO 时 - 将使用移动构造函数(如果可用)。
在引入移动语义之前,人们使用各种技术来模拟它们。其中之一实际上是 return 通过引用获取值。
一个原因是使用赋值运算符可以更容易地掌握每一行的作用。如果有一个函数调用somefunction(var1, var2, var3)
,不清楚其中有没有被修改。要找出答案,您必须实际阅读其他函数。
此外,如果您必须将向量作为参数传递给 fill()
,这意味着调用您的函数的每个地方都需要两行而不是一行:首先创建一个空向量,然后呼叫 fill()
.
另一个原因是移动构造函数允许函数 return 没有默认构造函数的 class 实例。考虑以下示例:
struct something{
something(int i) : value(i) {}
something(const something& a) : value(a.value) {}
int value;
};
something function1(){
return something(1);
}
void function2(something& other){
other.value = 2;
}
int main(void){
// valid usage
something var1(18);
// (do something with var1 and then re-use the variable)
var1 = function1();
// compile error
something var2;
function2(var2);
}
如果您关心效率,那么将您的 fill()
写入 return 一个值,或者将输出变量作为参数并不重要。您的编译器应该将其优化为这两者中最有效的替代方案。如果你怀疑没有,你最好测量一下。
我有点不明白你为什么要 use/need 移动构造函数。
如果我有以下内容:
vector fill(istream& is)
{
vector res;
for(double x; is >> x; res.push_back(x));
return res;
}
void bar()
{
vector vec = fill(cin);
// ... use vec ...
}
我可以通过添加 vector fill(istream& is, vector& res)
.
那么移动构造函数有什么意义呢?
假设你接下来将 std::vector<T>
放入 std::vector<std::vector<T>>
(如果你认为向量不应该嵌套,假设内部类型为 std::string
并假设我们正在讨论 std::string
的移动构造函数):即使你 可以 添加一个空对象并就地填充它,最终矢量将需要在调整大小时重新定位,此时移动元素派上用场。
请注意,从函数返回不是移动构造的主要动机,至少在效率方面不是:效率很重要构建代码以启用复制省略甚至通过避免移动来进一步提高性能。
尽管如此,移动构造函数在语义上可能仍然相关,因为返回要求类型是可复制的或可移动的。某些类型(例如流)不可复制,但它们是可移动的并且可以通过这种方式返回。
在您的示例中,编译器可能会应用 RVO - Return 值优化,这意味着您的函数将被内联,因此不会发生 return - 并且不会应用任何移动语义。仅当它不能应用 RVO 时 - 将使用移动构造函数(如果可用)。
在引入移动语义之前,人们使用各种技术来模拟它们。其中之一实际上是 return 通过引用获取值。
一个原因是使用赋值运算符可以更容易地掌握每一行的作用。如果有一个函数调用somefunction(var1, var2, var3)
,不清楚其中有没有被修改。要找出答案,您必须实际阅读其他函数。
此外,如果您必须将向量作为参数传递给 fill()
,这意味着调用您的函数的每个地方都需要两行而不是一行:首先创建一个空向量,然后呼叫 fill()
.
另一个原因是移动构造函数允许函数 return 没有默认构造函数的 class 实例。考虑以下示例:
struct something{
something(int i) : value(i) {}
something(const something& a) : value(a.value) {}
int value;
};
something function1(){
return something(1);
}
void function2(something& other){
other.value = 2;
}
int main(void){
// valid usage
something var1(18);
// (do something with var1 and then re-use the variable)
var1 = function1();
// compile error
something var2;
function2(var2);
}
如果您关心效率,那么将您的 fill()
写入 return 一个值,或者将输出变量作为参数并不重要。您的编译器应该将其优化为这两者中最有效的替代方案。如果你怀疑没有,你最好测量一下。