Wednesday, May 01, 2013

Copy constructor and return value optimization


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <stdio.h>

#include <string>

using std::string;

class MyString {
 public:
  MyString(const MyString& rhs) : value_(rhs.value()) {
    printf("copy ctor\n");
  }

  MyString(const string& value) : value_(value) {
    printf("ctor\n");
  }

  MyString& operator=(const MyString& rhs) {
    printf("assignment ctor\n");
    if (this != &rhs) {
      value_ = rhs.value();
    }
    return *this;
  }

  ~MyString() {
  }

  const string& value() const {
    return value_;
  }

  void set_value(const string& value) {
    value_ = value;
  }

 private:
  string value_;
};

MyString method1() {
  MyString my_string1 = MyString("true");
  return my_string1;
}

MyString method2(bool option) {
  MyString my_string1 = MyString("true");
  MyString my_string2 = MyString("false");
  return option ? my_string1 : my_string2;
}

MyString method3(bool option) {
  MyString my_string(option ? "true" : "false");
  return my_string;
}

MyString method4(bool option) {
  MyString my_string("false");
  my_string.set_value(option ? "true" : "false");
  return my_string;
}

int main(int argc, char* argv[]) {
  printf("calling method1\n");
  MyString my_string1 = method1();

  printf("\ncalling method2\n");
  MyString my_string2 = method2(true);

  printf("\ncalling method3\n");
  MyString my_string3 = method3(true);

  printf("\ncalling method4\n");
  MyString my_string4 = method4(true);

  return 0;
}

Let's compile it.
g++ -o rvo rvo.cc

Now run:
./rvo
calling method1
ctor 

calling method2 
ctor 
ctor 
copy ctor 

calling method3 
ctor 

calling method4 
ctor

Notice that line 64 may incur a call to the copy ctor as the compiler would need to make a temporary instance of MyString and copy the instance created on line 41 to that temporary variable. But as you can see from the execution results, there is no call to the copy constructor. In other words, the call to copy constructor is optimized away.

Whenever the compiler sees a temporary object being returned and assigned to another object of the same class, it tries to optimize away copy constructors and assignment constructors, which is what happened to method1/3/4. What happened to method2 is that there are two temporary objects and the compiler can not tell which one will be returned so the copy constructor has to be run.

Let's make it show up by disabling rvo.
g++ -o rvo rvo.cc -fno-elide-constructors

Run again:
calling method1 
ctor 
copy ctor 
copy ctor 
copy ctor 

calling method2
ctor 
copy ctor 
ctor 
copy ctor 
copy ctor 
copy ctor 

calling method3
ctor 
copy ctor 
copy ctor 

calling method4 
ctor 
copy ctor 
copy ctor

Now copy constructor is called.

Implication: Even if the copy constructor has user visible effect, it can be optimized away. So do not rely on it. Do a copy if you need a copy.

No comments: