Question

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;
}
Was it helpful?

Solution

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

OTHER TIPS

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.
}

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.

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;
}

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;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top