0

I don't understand in the following example why the paramater in the assignement operator use the copy constructor and not the move constructor to be builded

struct Foo
{
    int data;

    Foo()
    {
        static int cpt = 1;
        data = cpt++;
        std::cout <<  "Foo\n";
    }

    Foo(const Foo& foo)
    {
        std::cout <<  "const Foo&\n";
        data = foo.data; 
    }

    Foo(Foo&& foo)
    {
        std::cout <<  "Foo&&\n";
        data = foo.data;
        foo.data = 0;
    }

    Foo& operator= (Foo foo)  //<--- call the copy ctor and not the move ctor as I expected
    {
        data = foo.data;
    std::cout <<  "operator=\n";
    return *this;   
    }

    Foo& operator+ (const Foo& foo)
    {
        data += foo.data;
        return *this;
    }
};


int main()
{ 
    Foo f;
    Foo f1;
    Foo f3;

    f3 = f + f1;

    std::cout << f3.data;

    std::cin.ignore(); 

    return 1; 
}

output:

Foo
Foo
Foo
const Foo&
operator=
3

compiler : MSVCS2012 CTP

EDIT:

from What are move semantics?

"But if you say a = x + y, the move constructor will initialize that (because the expression x + y is an rvalue)..."

But in fact x + y is a rvalue only if operator + is implemented in a "special" way so ?

Community
  • 1
  • 1
Guillaume Paris
  • 10,303
  • 14
  • 70
  • 145
  • Answering "But in fact x + y is a rvalue only if operator + is implemented in a "special" way so ?": Yes, that's right. See my answer, in particular, the code that uses `bar` and its two operators + and -. – Cassio Neri Apr 02 '13 at 16:53

3 Answers3

3

Your assumption is incorrect. The assignment operator uses whichever constructor is available:

Foo a;
Foo b;

Foo x;

x = a;             // copy constructor
x = std::move(b);  // move constructor
x = Foo();         // move constructor

Your misunderstanding comes from your awkward operator+, which doesn't return an rvalue as you might be believing.

Update: A proper "plus" operator pair would look like this:

Foo& operator+= (const Foo& foo)
{
    data += foo.data;
    return *this;
}

Foo operator+ (const Foo& rhs)
{
    return Foo(*this) += rhs;
}

(In general, you could also make those signatures Foo rhs and use std::move(rhs) if appropriate, though it makes no difference for this simple class.)

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I see, how should I implement my operator+ so ? – Guillaume Paris Apr 02 '13 at 08:27
  • 1
    @Guillaume07: Make it return a `Foo`. – Kerrek SB Apr 02 '13 at 08:32
  • 2
    @Guillaume07: Better yet, implement `operator+=`, which *should* return `Foo&` and base `operator+` on `operator+=`. See my profile for two libraries (Boost.Operators and the move-aware df.operators) that can help you avoid errors and safe writing the same boilerplate code over and over again :) – Daniel Frey Apr 02 '13 at 08:42
  • @Kerrek: I have tried to switch the return value of `operator+` from `Foo&` to `Foo`, but same thing happen, `copy ctor` is called , any idea ? – Guillaume Paris Apr 02 '13 at 08:51
  • @Guillaume07: You're being multiply deceived. The interna of the `operator+` will presumably still cause a copy, and the move-assignment may simply elide the construction (which is legal). You can try and force your compiler to never elide constructions (e.g. `-fno-elide-constructors` in GCC) to see the effects, but otherwise just try some other examples. Compilers are usually trying very hard to make efficient code, so it can be a bit hard to track constructor calls. – Kerrek SB Apr 02 '13 at 08:52
  • (The corrected code with a proper `operator+` works as expected for me.) – Kerrek SB Apr 02 '13 at 08:55
  • @KerrekSB: `return Foo(*this) += rhs;` prevents the RVO from being applies and is thus inefficient. See the mentioned libraries for a correct (efficient, (N)RVO-enabled) implementation, the Boost documentation also has an [explanation](http://www.boost.org/doc/libs/1_53_0/libs/utility/operators.htm#symmetry). – Daniel Frey Apr 02 '13 at 09:00
  • 1
    @DanielFrey: Honestly, I would not worry about this level of micro-optimization unless specifically pointed at by a profiler. It's normal for Boost to worry about it because its users would be disappointed should they skirt it because it's inefficient, but it does not mean that you should "obfuscate" your code as much as it does in the hope of achieving the same level of efficiency... unless it really matters. – Matthieu M. Apr 02 '13 at 09:07
  • @MatthieuM.: Thus, use the libraries. It makes your code so much easier *and* gives you all the efficiency for free. – Daniel Frey Apr 02 '13 at 09:09
  • @DanielFrey: Well... boost.operators is a bit cryptic; I have used it, but it's baffled a number of coworkers, especially the difference between symmetric and asymmetric operators. – Matthieu M. Apr 02 '13 at 09:15
  • @KerrekSB: at least under visual `Foo& operator= (Foo foo)` still call the copy ctor when called with `f3=f1+f` even with your implementation of `operator +` and `operator +=` – Guillaume Paris Apr 03 '13 at 08:17
  • @Guillaume07: Yes, but that's the copy inside the `operator+`... the construction of the argument of `operator=` is elided altogether. – Kerrek SB Apr 03 '13 at 08:20
  • @MatthieuM. makes a good point. I'd just add that this goes doubly for c++ that's being developed by multiple people who are not in close proximity. eschew obfuscation :) – Max DeLiso Apr 03 '13 at 15:36
1

As it has been pointed out by others, the problem is operator +(). Specifically, it returns a Foo&, that is, an lvalue reference. Since an lvalue reference is an lvalue (this is not a tautology) it cannot be bound to an rvalue reference. Therefore, Foo(Foo&&) cannot be called.

I'm not going to say how the operator +() and operator +=() should be implemented (see other answers and the comments therein for more on this topic). I'm going to focus on rvalue x lvalue. As said in Scott Meyers' Universal References in C++11

A precise definition for these terms is difficult to develop (the C++11 standard generally specifies whether an expression is an lvalue or an rvalue on a case-by-case basis) ...

However, the following functions can be used to check if an object is an lvalue or rvalue:

#include <type_traits>

template <typename T>
bool is_lvalue(T&&) {
    return std::is_reference<T>::value;
}

template <typename T>
bool is_rvalue(T&&) {
    return !std::is_reference<T>::value;
}

For example, the following code

struct bar {
    bar&  operator+(const bar&) { return *this;  }
    bar&& operator-(const bar&) { return std::move(*this); }
};

int main() {

    bar b1;
    bar b2;

    std:: cout << std::boolalpha;

    std:: cout << is_rvalue( b1 + b2) << std::endl;
    std:: cout << is_rvalue( b1 - b2) << std::endl;

}

outputs

false
true

Note: bar, inspired by the original's code, uses operator +() and operator -() but for this matter there's nothing special about being operators.

Functions is_lvalue() and is_rvalue() can be improved. For instance they can be constexrp but I'm keeping it simple here because some compilers (notably, VS2012) don't implement constexpr yet.

The explanation on how these function works, is based on how T is deduced. I quote the aforementioned Scott Meyers' article:

During type deduction for a template parameter [ ... ], lvalues and rvalues of the same type are deduced to have slightly different types. In particular, lvalues of type T are deduced to be of type T& (i.e., lvalue reference to T), while rvalues of type T are deduced to be simply of type T.

Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
0

You need to provide two overloads for operator= because your code does not move data:

Foo& operator= (const Foo& foo)
{
    data = foo.data;
    return *this;   
}

// only needed if the implementation can benefit from a movable object
Foo& operator= (Foo&& foo)
{
    data = std::move(foo.data); // without std::move, as above, the copy-ctor is called
    return *this;   
}

That said, what is wrong with this signature:

Foo& operator= (Foo foo);

? The problem is, that you always force an object to be passed, which can lead to inefficiencies. Consider:

Foo f, f2;
f = f2;

(of course, in the example the optimizer will most likely remove all overhead).

Generally, operator= does not need a copy, so it shouldn't request one. See also Andy Prowl's nice take on how to take parameters "correctly".

Community
  • 1
  • 1
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • 1
    No, that's not necessary. In fact, the OP's code is much better. – Kerrek SB Apr 02 '13 at 08:56
  • @KerrekSB Even for a movable parameter which omitted the copy of `foo`, the OP still calls `data = foo.data`, which is always a copy. You forgot to make the move transitive to the members. (OK, in the example, it's only an `int`, but I was thinking generally) Also, the code is not always better, as it forces a copy for `foo` even it no copy is needed at all, e.g. `f3 = f;`. – Daniel Frey Apr 02 '13 at 09:04