4

I wrote the code below to check that popular C++03 compilers implement RVO whenever possible. (See my related question about RVO Return value optimization: ho can I avoid copy construction of huge STL containers.).

As far as I understand the short answer is no. But I'm not sure I understand.

I have the following five questions about the std::vector implementation of clang gcc and VS9, read the code and the output below:

  1. Why is compiler C Fun1 copying twice the vector?
  2. Why both compiler B and C always construct at least one non-requested element?
  3. Why this non requested element is made twice with compiler C Fun2?
  4. Why reserving space yields one more non requested element for both compilers B and C?
  5. Is all this standard compliant?
  6. Bonus question: Does all this corresponds to user's expectations?

#include <iostream>
#include <vector>
#include <cassert>

#define SIZE (3) 

class NRVO{
public:
  NRVO() : name_(-1){}
  ~NRVO(){
    std::cout << "Destroy "<< name_ << "\n";
  }
  void set_name(){name_ = counter++;}
private:
  int name_;
  static int counter; 
};

int NRVO::counter = 0;

std::vector<NRVO> fun1(){
  std::vector<NRVO> vec(SIZE);  
  for(std::vector<NRVO>::iterator v_it = vec.begin();
      v_it != vec.end();
      ++v_it){
    v_it->set_name();
  }
  return vec;
}

void fun2(std::vector<NRVO>& vec){
  vec.clear();
  vec.resize(SIZE);  
  for(std::vector<NRVO>::iterator v_it = vec.begin();
      v_it != vec.end();
      ++v_it){
    v_it->set_name();
  }
  return;
}

int main(){
  {
    std::vector<NRVO> myNrvo1;
    std::cout << "Fun1\n";
    myNrvo1 = fun1();
    assert(myNrvo1.size()==SIZE);
  }
  {
    std::vector<NRVO> myNrvo2;
    std::cout << "Fun2\n";
    fun2(myNrvo2);
    assert(myNrvo2.size()==SIZE);
  }
  {
    std::vector<NRVO> myNrvo3;
    myNrvo3.reserve(SIZE);
    std::cout << "Fun3\n";
    fun2(myNrvo3);
    assert(myNrvo3.size()==SIZE);
  }
  return 0;
}

Output with popular C++11 compiler A

Fun1
Destroy 0
Destroy 1
Destroy 2
Fun2
Destroy 3
Destroy 4
Destroy 5
Fun3 
Destroy 6
Destroy 7
Destroy 8

Output with popular C++03 compiler B

Fun1
Destroy -1
Destroy 0
Destroy 1
Destroy 2
Destroy 0
Destroy 1
Destroy 2
Fun2
Destroy -1
Destroy 3
Destroy 4
Destroy 5
Fun3 
Destroy -1
Destroy -1
Destroy 6
Destroy 7
Destroy 8

Output with popular C++03 compiler C

Fun1
Destroy -1
Destroy 0
Destroy 1
Destroy 2
Destroy 0
Destroy 1
Destroy 2
Destroy 0
Destroy 1
Destroy 2
Fun2
Destroy -1
Destroy -1
Destroy 3
Destroy 4
Destroy 5
Fun3
Destroy -1
Destroy -1
Destroy -1
Destroy 6
Destroy 7
Destroy 8
Community
  • 1
  • 1
jimifiki
  • 5,377
  • 2
  • 34
  • 60
  • 1) I suspect compiler C to use a temporary. `vec` is copied to temporary; temporary is assigned to `myNrvo1`. You might test this by ignoring the return value of `fun1`: `std::cout << "Fun1\n"; (void) fun1();` – YSC Nov 13 '15 at 11:30
  • @OP : Do you see copies happening ? – Arunmu Nov 13 '15 at 11:51
  • @Arunmu: we see copies' destruction ;) – YSC Nov 13 '15 at 12:04
  • 2
    Did you build release builds for all of these compilers? To aid in debugging, it's nice for temporaries to be there, so debug builds will often not do such optimisations. – MicroVirus Nov 13 '15 at 12:14
  • Try 'std::vector myNrvo1 = fun1();' in main. – Arunmu Nov 13 '15 at 12:54
  • Without C++11 turned out, g++(4.8.x) in my case misses out to do direct initialization when the vector is instantiated in advance i.e before call site – Arunmu Nov 13 '15 at 12:56

0 Answers0