3

I followed the amazing tutorials from stackoverflow for Move and Operator overloading (e.g. What are the basic rules and idioms for operator overloading?), and the following situation is baffling me. Nothing fancy in the code, just printing when special member functions are called.

The main code:

    class B {
public:
    B() { std::cout << "B::ctor\n"; }

    ~B() { std::cout << "B::dtor\n"; }

    B(B const &b) {
        std::cout << "B::copy ctor\n";
    }

    B &operator=(B const &rhs) {
        std::cout << "B::copy assignment\n";
        return *this;
    }

    B(B &&b) {
        std::cout << "B::move ctor\n";
    }

    B &operator=(B &&rhs) {
        std::cout << "B::move assignment\n";
        return *this;
    }

    B &operator+=(B const &rhs) {
        std::cout << "B::operator+=\n";
        return *this;
    }
};



int main() {
  B b;
  std::cout << "=== b = b + b + b ===\n";
  b = b + b + b;
}

Now, two scenarios, where in each I define the operator + differently:

B operator+(B p1, B const &p2) {
    std::cout << "B::operator+\n";
    return p1 += p2;
}

with output for the whole program:

B::ctor
=== b = b + b + b ===
B::copy ctor
B::operator+
B::operator+=
B::copy ctor
B::operator+
B::operator+=
B::copy ctor
B::move assignment
B::dtor
B::dtor
B::dtor
B::dtor

and the second scenario:

B operator+(B p1, B const &p2) {
    std::cout << "B::operator+\n";
    p1 += p2;
    return p1;
}

with output:

B::ctor
=== b = b + b + b ===
B::copy ctor
B::operator+
B::operator+=
B::move ctor
B::operator+
B::operator+=
B::move ctor
B::move assignment
B::dtor
B::dtor
B::dtor
B::dtor

How come the second scenario does give the expected result, using correctly the move semantics, but the first makes copy everywhere?

I just want to add that the second scenario is the one recommended in the tutorials I read (like the link from above), but when I tried to implement it, I intuitively wrote the first scenario and it gave me the wrong behaviour...

Marine Galantin
  • 1,634
  • 1
  • 17
  • 28
  • 1
    Good question. You may want to add the `language-lawyer` tag to this question. Changing the first scenario to `return std::move(p1 += p2);` does the expected thing, if that may be of interest. – Eljay Feb 10 '22 at 18:26

1 Answers1

3

Returning a local variable of type T from a function with with the same1 return type T is a special case.

It at least automatically moves the variable, or, if the compiler is smart enough to perform so-called NRVO, eliminates the copy/move entirely and constructs the variable directly in the right location.

Function parameters (unlike regular local variables) are not eligible for NRVO, so you always get an implicit move in (2).

This doesn't happen in (1). The compiler isn't going to analyze += to understand what it returns; this rule only works when the operand of return is a single variable.

Since += returns an lvalue reference, and you didn't std::move it, the copy constructor is called.


1 Or a type that differs only in cv-qualifiers.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • I found this question https://stackoverflow.com/questions/6233879/move-or-named-return-value-optimization-nrvo but NRVO is obscure to me. Can you detail a bit what is happening please? – Marine Galantin Feb 10 '22 at 18:34
  • 1
    I suspect NRVO can only be done with a local variable, and not a local parameter. – Eljay Feb 10 '22 at 18:37
  • 1
    @MarineGalantin Normally, when a function returns by value (has a non-reference return type), `return` creates a temporary object, which becomes the result of calling the function. NRVO is a way for a compiler to avoid creating that temporary, by combining the local variable (that it knows is going to be returned) and the temporary into one object. So it's as if the temporary is created early, and the local variable refers to that temporary. – HolyBlackCat Feb 10 '22 at 18:40
  • 1
    But none of that matters in your case, since NRVO doesn't happen. It doesn't work on function parameters, only on other local variables. – HolyBlackCat Feb 10 '22 at 18:41