在 C++ 中,如何 return 多个对象并从 RVO 中受益
In c++, how to return multiple objects and nevertheless benefit from RVO
我的函数需要 return 几个大容器。为此,return 语句创建了一个元组,其中包含 return 的容器。但是,根据我的测试(使用 Apple clang 版本 11.0.0,clang-1100.0.33.17),可能会在构造元组时制作副本。在调用函数中,returned 元组使用结构化绑定分配给多个变量。
如果我的函数 return 只编辑一个容器,将使用 RVO,并且不会制作任何副本。当函数 return 有多个容器时,是否有类似的避免创建副本的好方法?
示例如下:
#include <tuple>
#include <vector>
using namespace std;
tuple<vector<int>, vector<double>> f(){
vector<int> a(1);
vector<double> b(1);
return tuple(a,b);
}
int main(){
auto [x, y] = f();
}
如果你尝试移动怎么办?
tuple<vector<int>, vector<double>> f(){
vector<int> a(1);
vector<double> b(1);
return tuple(std::move(a), std::move(b));
}
或者更简单地说,从临时对象构造元组(这也应该导致移动构造):
using vector_tuple = tuple<vector<int>, vector<double>>;
vector_tuple f(){
return std::make_tuple<vector_tuple>(vector<int>(1), vector<double>(1));
}
您可以构建元组中已有的向量。
tuple<vector<int>, vector<double>> f(){
tuple ret(vector<int>(1), vector<double>(1));
// Now usable by std::get<N>(ret);
auto &[a,b] = ret;
// Now you can use a and b, no copies made.
return ret;
}
虽然这与其他答案非常相似,但我想添加带有结构化绑定的部分,并解释为什么如果您想在没有任何移动的情况下拥有 RVO,那么您基本上必须以这种方式或非常类似的方式进行操作等等
对于 RVO,编译器基本上添加了一个不可见的输出参数。它在调用时已经为 returned 值分配了最终需要的 space 并将该位置传递给被调用者。然后被调用者将使用 space 作为获得 returned 的变量。因此,对于 RVO,必须在函数中创建 return 值,并且以下不会执行 RVO:
std::string foo(int i) {
std::string ret1{"Hi"}; // Which one should use the space?
std::string ret2{"Hello"};
if( i > 0 ) { // We only know here
return ret1;
} else {
return ret2;
}
}
这也是为什么多个 return 值在创建时必须是同一对象的一部分的原因。分配的 space 完全符合 return 类型,你不能只取两个独立的对象并在此 space 中构造它们,即使你知道它们稍后将在 [=20] 中传递=]编辑对象。
我的函数需要 return 几个大容器。为此,return 语句创建了一个元组,其中包含 return 的容器。但是,根据我的测试(使用 Apple clang 版本 11.0.0,clang-1100.0.33.17),可能会在构造元组时制作副本。在调用函数中,returned 元组使用结构化绑定分配给多个变量。
如果我的函数 return 只编辑一个容器,将使用 RVO,并且不会制作任何副本。当函数 return 有多个容器时,是否有类似的避免创建副本的好方法?
示例如下:
#include <tuple>
#include <vector>
using namespace std;
tuple<vector<int>, vector<double>> f(){
vector<int> a(1);
vector<double> b(1);
return tuple(a,b);
}
int main(){
auto [x, y] = f();
}
如果你尝试移动怎么办?
tuple<vector<int>, vector<double>> f(){
vector<int> a(1);
vector<double> b(1);
return tuple(std::move(a), std::move(b));
}
或者更简单地说,从临时对象构造元组(这也应该导致移动构造):
using vector_tuple = tuple<vector<int>, vector<double>>;
vector_tuple f(){
return std::make_tuple<vector_tuple>(vector<int>(1), vector<double>(1));
}
您可以构建元组中已有的向量。
tuple<vector<int>, vector<double>> f(){
tuple ret(vector<int>(1), vector<double>(1));
// Now usable by std::get<N>(ret);
auto &[a,b] = ret;
// Now you can use a and b, no copies made.
return ret;
}
虽然这与其他答案非常相似,但我想添加带有结构化绑定的部分,并解释为什么如果您想在没有任何移动的情况下拥有 RVO,那么您基本上必须以这种方式或非常类似的方式进行操作等等
对于 RVO,编译器基本上添加了一个不可见的输出参数。它在调用时已经为 returned 值分配了最终需要的 space 并将该位置传递给被调用者。然后被调用者将使用 space 作为获得 returned 的变量。因此,对于 RVO,必须在函数中创建 return 值,并且以下不会执行 RVO:
std::string foo(int i) {
std::string ret1{"Hi"}; // Which one should use the space?
std::string ret2{"Hello"};
if( i > 0 ) { // We only know here
return ret1;
} else {
return ret2;
}
}
这也是为什么多个 return 值在创建时必须是同一对象的一部分的原因。分配的 space 完全符合 return 类型,你不能只取两个独立的对象并在此 space 中构造它们,即使你知道它们稍后将在 [=20] 中传递=]编辑对象。