为什么 RVO 不在这里发生?
Why RVO doesn't happen here?
我已经阅读了戴夫亚伯拉罕关于RVO的文章和其他一些关于SO的Q/As(14043609, 9293726和10818278),但我仍然有一个问题。当我编译并运行以下代码时,得到如下输出:
Address of v in func 0x7fffac6df620
Address of v.data in func 0x2081010
Address of v in main 0x7fffac6df690
Address of v.data in func 0x20811b0
9
对我来说,这似乎是一个副本。我如何通过函数的大对象?请注意,我希望返回一个或多个对象,而不为其编写显式结构。我使用带有-O2的GCC 4.6.3。编辑:前两个答案表明我对编译器期望过高。我添加了一个main2,它的行为方式相同,例如打印的地址不同。我想强调的是,动机是大对象的有效返回。
#include <iostream>
#include <vector>
#include <tuple>
std::tuple<std::vector<int>, double> func() {
std::vector<int> v;
v.reserve(100);
for (int k=0;k!=100;k+=1)
v.push_back(k);
double a = 5.0;
std::cout << "Address of v in funct" << &v << std::endl;
std::cout << "Address of v.data in funct" << v.data() << std::endl;
return make_tuple(v, a);
}
int main() {
std::vector<int> v;
double a;
std::tie(v, a) = func();
std::cout << "Address of v in maint" << &v << std::endl;
std::cout << "Address of v.data in funct" << v.data() << std::endl;
std::cout << v[9] << std::endl;
return 0;
}
int main2() {
auto tp = func();
std::vector<int> & v = std::get<0>(tp);
double & a = std::get<1>(tp);
std::cout << "Address of v in maint" << &v << std::endl;
std::cout << "Address of v.data in funct" << v.data() << std::endl;
std::cout << v[9] << std::endl;
return 0;
}
如前所述,有两件事可以防止RVO。该函数不返回v
,而是一个由v
和a
构造的元组。在main函数中,v
是赋值的,而不是根据返回值构造的。
要得到你想要的,你可以直接使用元组而不需要额外的向量对象:
#include <iostream>
#include <vector>
#include <tuple>
std::tuple<std::vector<int>, double> func() {
std::tuple<std::vector<int>, double> t;
get<0>(t).reserve(100);
for (int k=0;k!=100;k+=1)
get<0>(t).push_back(k);
get<1>(t) = 5.0;
std::cout << "Address of v in funct" << &get<0>(t) << std::endl;
std::cout << "Address of v.data in funct" << get<0>(t).data() << std::endl;
return t;
}
int main()
{
std::tuple<std::vector<int>, double> t = func();
std::cout << "Address of v in maint" << &get<0>(t) << std::endl;
std::cout << "Address of v.data in funct" << get<0>(t).data() << std::endl;
std::cout << get<0>(t)[9] << std::endl;
return 0;
}
输出:Address of v in func 0x28fe80
Address of v.data in func 0x962c08
Address of v in main 0x28fe80
Address of v.data in func 0x962c08
9
另一种优化是在构造元组时使用move语义:
return make_tuple(std::move(v), a);
在这种情况下,至少避免了复制vector的内部缓冲区:
Address of v in func 0x28fdd4
Address of v.data in func 0xa72c08
Address of v in main 0x28fe64
Address of v.data in func 0xa72c08
9
由于v
和a
都在main()
中被声明为变量,因此没有副本可以省略。你在这里得到的是拷贝分配,而不是拷贝构造。它相当于:
struct Foo {};
Foo foo() { return Foo(); }
int main()
{
Foo f1;
f1 = foo(); // no copy hence f1 is distinct from object returned
Foo f2 = foo(); // We can get RVO here, returned object can be f2.
}
RVO很可能在这里发生,但是在您给出的代码中复制省略的唯一机会是将make_tuple(v, a)
的返回值复制到func()
的返回值中。
无论是否这样做,std::vector
和double
仍然会被复制。您只是将func()
的结果分配给main
中的v
和a
。复制省略(和RVO)仅适用于复制/移动构造,而不适用于赋值。
当您在main
中执行&v
时,您只是获得main
第一行中定义的v
对象的地址。当然,这与func
中定义的v
对象不同。
在第一个示例中,数据被复制到赋值中:
int main() {
std::vector<int> v;
double a;
std::tie(v, a) = func();
在第二个示例中,创建元组时仍然复制数据。这个修改后的示例显示了RVO确实发生了:
#include <iostream>
#include <vector>
#include <tuple>
std::tuple<std::vector<int>, double> func() {
std::vector<int> v;
v.reserve(100);
for (int k=0;k!=100;k+=1)
v.push_back(k);
double a = 5.0;
const auto ret = make_tuple(v, a);
const auto &v1 = std::get<0>(ret);
std::cout << "Address of v in funct" << &v1 << std::endl;
std::cout << "Address of v.data in funct" << v1.data() << std::endl;
return ret;
}
int main() {
auto tp = func();
std::vector<int> & v = std::get<0>(tp);
double & a = std::get<1>(tp);
std::cout << "Address of v in maint" << &v << std::endl;
std::cout << "Address of v.data in funct" << v.data() << std::endl;
std::cout << v[9] << std::endl;
(void)a;
}
谢谢你的回答。我发现提莫的回答很有帮助。这就是我如何根据自己的风格调整答案。注意func
和main
中重复的样板文件。当然,如果有人知道如何摆脱它,那就太好了!
#include <iostream>
#include <vector>
#include <tuple>
std::tuple<std::vector<int>, double> func() {
std::tuple<std::vector<int>, double> tp;
std::vector<int> & v = std::get<0>(tp);
double & a = std::get<1>(tp);
v.reserve(100);
for (int k=0;k!=100;k+=1)
v.push_back(k);
a = 5.0;
std::cout << "Address of v in funct" << &v << std::endl;
std::cout << "Address of v.data in funct" << v.data() << std::endl;
return tp;
}
int main() {
std::tuple<std::vector<int>, double> tp = func();
std::vector<int> & v = std::get<0>(tp);
double & a = std::get<1>(tp);
std::cout << "Address of v in maint" << &v << std::endl;
std::cout << "Address of v.data in funct" << v.data() << std::endl;
std::cout << v[9] << std::endl;
(void)a;
return 0;
}
- 努力将整数转换为链表。不知道我在这里做错了什么
- 我可以在这里替换什么,因为我不能在 C# 中使用隐式变量的 lambda 函数?
- 当我从下面的代码中删除关键字 virtual 时,它可以正常工作,否则会出现错误。在这里"virtual"字的意义是什么?
- File.cpp.o:OpenPose 标志 CMakeFiles/.. 的多重定义/main.cpp.o:首先在这里定
- 为什么thread_local变量在这里从未初始化?
- 为什么我必须在这里使用dynamic_cast
- 在这里,当我们比较 if(vc[i]==vc1[i]) 时,它是向量数组. 实际上比较的值是多少,
- 我正在尝试使用 while 循环从字符串中删除字母,直到没有字母。我在这里做错了什么?
- 为什么 C++20 中的 [[可能]] 属性在这里引发警告?
- 我在这里正确传递参数了吗?
- 为什么gmp会在这里与"invalid next size"重新定位一起崩溃?
- 移动语义在这里如何工作?
- 如何在这里循环运行?
- 为什么枚举变量在这里是右值?
- 我的C++合并排序代码不起作用。我在这里错过了什么?
- 试图美化这个Arduino代码[初学者在这里]
- 复制交换习惯用法-我们可以在这里使用动态强制转换操作吗
- 在这里使用删除运算符是否正确,我很困惑
- 为什么g++在这里不启用RVO
- 为什么 RVO 不在这里发生?