Home > Software engineering >  C Move constructor not called with the compound operator = when written in one line
C Move constructor not called with the compound operator = when written in one line

Time:02-11

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...

CodePudding user response:

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.

  • Related