当从C++中的函数返回值时,我们提供了复制省略和(命名)返回值优化,帮助我们创建更高效的代码。简言之,以下代码:
std::vector<int> make_vec_1(){
std::vector<int> v;
v.resize(1e6);
return v;
}结果是无声地移动或直接构造到返回值的目的地,而不是副本。这方面的规则还意味着,返回时显式移动返回的对象实际上会阻止这些优化。
std::vector<int> make_vec_2(){
std::vector<int> v;
v.resize(1e6);
return std::move(v); // BAD
}此版本防止RVO,如Scott的“有效现代C++”第25项中所解释的那样。
我的问题是,当返回类型不同,但可以从一个或多个局部变量移动时,会发生什么?考虑以下函数,每个函数都返回一个可选向量:
std::optional<std::vector<int>> make_opt_vec_1(){
std::vector<int> v;
v.resize(1e6);
return v; // no move
}
std::optional<std::vector<int>> make_opt_vec_2(){
std::vector<int> v;
v.resize(1e6);
return std::move(v); // move
}其中哪一个是正确的?在我看来,行return std::move(v)一开始就像是一个危险的标志,但我也怀疑这是正确的做法。对于返回一对向量的以下两个函数也是如此:
std::pair<std::vector<int>, std::vector<int>> make_vec_pair_1(){
std::vector<int> v1, v2;
v1.resize(1e6);
v2.resize(1e6);
return {v1, v2}; // no move
}
std::pair<std::vector<int>, std::vector<int>> make_vec_pair_2(){
std::vector<int> v1, v2;
v1.resize(1e6);
v2.resize(1e6);
return {std::move(v1), std::move(v2)}; // move
}在这种情况下,尽管乍一看看起来很奇怪,但我认为进入返回值是更好的做法。
当类型不同时,移到返回值中更好,但返回值可以从要移出的局部变量中移动,对吗?我是否误解了NRVO,还是这里还有其他的优化在我前面?
发布于 2019-06-06 19:55:18
当类型不同时,移到返回值中更好,但返回值可以从要移出的局部变量中移动,对吗?我是否误解了NRVO,还是这里还有其他的优化在我前面?
你确实错过了一件事。即使类型不同,也会自动进行隐式移动。
class.copy.elision (重点地雷) 3.在以下副本初始化上下文中,可以使用移动操作而不是复制操作:
首先执行重载解析以选择副本的构造函数,就好像该对象是由rvalue指定的一样。如果第一个重载解析失败或没有执行,或者所选构造函数的第一个参数的类型不是对对象类型(可能是cv限定的)的rvalue引用,则将再次执行重载解析,将对象视为lvalue。 注意:无论复制是否会发生,这种两阶段过载解决方案都必须执行.如果不执行省略,它将确定要调用的构造函数,即使调用被省略,所选的构造函数也必须是可访问的。 - 尾注
这不取决于类型匹配,并且是在不发生full (N)RVO的情况下的回退行为。因此,在make_opt_vec_2中显式移动不会获得任何好处。
考虑到std::move要么是悲观的,要么是完全多余的,我认为最好在简单地返回函数本地对象时不要这样做。
唯一想要显式写入移动的情况是,返回的表达式更加复杂。在这种情况下,你确实是一个人,不动是一种潜在的悲观。因此,在make_vec_pair_2中,移动成对是正确的。
这里的经验法则是不要只移动一个id表达式,它是一个函数本地对象。否则,走开。
https://stackoverflow.com/questions/56484125
复制相似问题