Reputation: 521
I have read Dave Abrahams article on RVO and a few other Q/As on SO (14043609, 9293726 and 10818278) but I still have a question. When I compile and run the following code, I get this output:
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
To me it seems that a copy is made. How do I pass large objects out of functions? Please note that I want to return one or more objects without writing an explicit structure for it. I used GCC 4.6.3 with -O2. Edit: The first two answers showed me that I expected too much from the compiler. I added a main2 that behaves in the same way, e.g. the printed addresses are different. I would like to emphasize that the motivation is efficient return of large objects.
#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 func\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << 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 main\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << 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 main\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << v.data() << std::endl;
std::cout << v[9] << std::endl;
return 0;
}
Upvotes: 4
Views: 792
Reputation: 521
Thank you for your answers. I found Timo's answer most helpful. This is how I adapted that answer to my own style. Note the duplicated boilerplate in both func
and main
. Of course, if someone knows how to get rid of that it would be great!
#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 func\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << 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 main\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << v.data() << std::endl;
std::cout << v[9] << std::endl;
(void)a;
return 0;
}
Upvotes: 0
Reputation: 64213
In your first example data is copied in the assignment :
int main() {
std::vector<int> v;
double a;
std::tie(v, a) = func();
In your second example, data is still copied when you make a tuple. This modified example shows that the RVO really happens :
#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 func\t" << &v1 << std::endl;
std::cout << "Address of v.data in func\t" << 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 main\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << v.data() << std::endl;
std::cout << v[9] << std::endl;
(void)a;
}
Upvotes: 2
Reputation: 5176
As already said, there are two things that prevent RVO. The function doesn't return v
, but instead a tuple that is constructed form v
and a
. Also in main function v
is assigned and not constructed from the return value.
To get what you want you could use the tuples directly without additional vector objects:
#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 func\t" << &get<0>(t) << std::endl;
std::cout << "Address of v.data in func\t" << get<0>(t).data() << std::endl;
return t;
}
int main()
{
std::tuple<std::vector<int>, double> t = func();
std::cout << "Address of v in main\t" << &get<0>(t) << std::endl;
std::cout << "Address of v.data in func\t" << get<0>(t).data() << std::endl;
std::cout << get<0>(t)[9] << std::endl;
return 0;
}
Output:
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
Alternative optimization is to use move semantics when constructing the tuple:
return make_tuple(std::move(v), a);
In this case at least copying the vector's internal buffer is avoided:
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
Upvotes: 4
Reputation: 227370
Since both v
and a
have been declared as variables in main()
, there is no copy to elide. What you get here is copy assignment, not copy construction. It is the equivalent of this:
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.
}
Upvotes: 3
Reputation: 110648
RVO may very well be happening here but the only opportunity for copy elision in the code you've given is copying the return value of make_tuple(v, a)
into the return value of func()
.
Regardless of whether this is done or not, the std::vector
and double
would still be copied. You are just assigning from the result of func()
to v
and a
in main
. Copy elision (and RVO) only apply to copy/move construction, not assignment.
When you do &v
in main
, you are just getting the address of the v
object defined in the first line of main
. Of course this is different from the v
object defined in func
.
Upvotes: 2