c++c++17move-semanticslvalue

Question about copy constructor in return value


This is a code test from this. I reproduced some of them as below:

//struct definition. All kinds of prints
struct Snitch {   // Note: All methods have side effects
  Snitch() { cout << "c'tor" << endl; }
  ~Snitch() { cout << "d'tor" << endl; }

  Snitch(const Snitch&) { cout << "copy c'tor" << endl; }
  Snitch(Snitch&&) { cout << "move c'tor" << endl; }

  Snitch& operator=(const Snitch&) {
    cout << "copy assignment" << endl;
    return *this;
  }

  Snitch& operator=(Snitch&&) {
    cout << "move assignment" << endl;
    return *this;
  }
};
//end struct def

Snitch ReturnObj() {
  Snitch snit;
  cout<< "before return ReturnObj" << endl;
  return snit;  
}

int main() { 
  Snitch snitchobj1 = ReturnObj();  
  cout<< "end main" << endl;
}

I disabled RVO as the author: g++ -fno-elide-constructors -o rvo.exe .\rvo_or_not.cpp and run rvo.exe in order to find what exactly happens.

I got:

c'tor
before return ReturnObj
move c'tor //I didn't know why this is move c'tor instead of copy c'tor
d'tor
move c'tor
d'tor
end main
d'tor

The first printing of move c'tor is unexpected for me. The line "Snitch snit;" in function defines a local lvalue snit . So when returned, the copy construtor should be called to initialize the temporary object? Am I right? Or it's an xvalue actually not a lvalue?


Solution

  • When returning a local variable by value the compiler will prefer to move it over doing a copy if it can't do named return value optimization, as in your case since you've turned NRVO (named return value optimization) off - URVO (or unnamed RVO) can't be turned off, at least not while in a strict conformance mode.

    the copy construtor should be called to initialize the temporary object

    There is no temporary object that I can see. The output I get from g++, clang++ and icx in C++17 mode with -fno-elide-constructors has only one move:

    c'tor
    before return ReturnObj
    move c'tor
    d'tor
    end main
    d'tor
    

    In the link you showed, they compile in C++11 mode:

    clang++ -std=c++11 -fno-elide-constructors
    

    Mandatory URVO wasn't a thing until C++17. In C++11 and 14 both URVO and NRVO is allowed but not mandatory. You can turn off both RVO versions in C++11 and 14 with -fno-elide-constructors, but in C++17 URVO is mandatory and can't be turned off while NRVO can be.