3

I made a program to assess the performance differences between doing this:

func3(func2(func1()));

vs this:

retval1 = func1();
retval2 = func2(retval1);
func3(retval2);

I prefer the latter for readability and ease of debugging, and I wanted to know if the compiler (MSVC 12.0) would optimize away the intermediate objects in a release build. My test program is this:

#include <iostream>

using namespace std;

struct Indicator {
    Indicator() {cout << "Default constructor" << endl;}
    Indicator(const Indicator& other) {cout << "Copy constructor" << endl;}
    const Indicator& operator=(const Indicator& other) {cout << "Assignment operator" << endl;}
    ~Indicator() {cout << "Destructor" << endl;}
};

Indicator func1()
{return Indicator();}

Indicator func2(Indicator&& i)
{return std::move(i);}

Indicator func3(Indicator&& i)
{return std::move(i);}

int main() {
    Indicator i = func3(func2(func1()));
    cout << &i << endl;
    return 0;
}

I was surprised to see that even with -O2, there are still three instances of Indicator being created:

Default constructor
Copy constructor
Copy constructor
Destructor
Destructor
00000000002EFC70
Destructor
Press <RETURN> to close this window...

This conflicts with my understanding of move semantics, which is that there should only be one instance of Indicator created in this case. I also thought that the compiler should be able to use NRVO for the chained function calls. Can someone explain to me what's going on here?

Carlton
  • 4,217
  • 2
  • 24
  • 40

1 Answers1

6

Based on the rule of 5 when you defined your copy constructor and copy assignment operator, you disabled the compiler-generated move constructor and move assignment operator.

If you define your move constructor, you will get the output you expect.

Indicator(Indicator&& other) {cout << "Move constructor" << endl;}
Indicator& operator=(Indicator&& other) {cout << "Move assignment operator" << endl;}

Working demo

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
  • 1
    It is worth to note that Visual C++ 12 does not generate implicit move constructor at all. – stgatilov Jul 20 '15 at 13:46
  • Ahhh. Yes, I see. I just got used to adhering to the rule of 3, now I have to remember to define move constructors as well... – Carlton Jul 20 '15 at 14:05
  • 1
    @Carlton Or stick to the rule of zero! If you don't **have to** define one (or some) of them, then let the compiler make them for you! – Cory Kramer Jul 20 '15 at 14:19