Search code examples
c++return-value-optimization

Why RVO doesn't happen here?


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

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