Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11: Move/Copy construction ambiguity?

Tags:

c++

c++11

In C++11 we can define copy and move constructors, but are both allowed on the same class? If so, how do you disambiguate their usage? For example:

Foo MoveAFoo() {
  Foo f;
  return f;
}

Is the above a copy? A move? How do I know?

like image 627
void.pointer Avatar asked Feb 05 '12 20:02

void.pointer


People also ask

What is the difference between copy and move constructor?

If any constructor is being called, it means a new object is being created in memory. So, the only difference between a copy constructor and a move constructor is whether the source object that is passed to the constructor will have its member fields copied or moved into the new object.

What is copy constructor and under what circumstances is it called?

A copy constructor is called when a new object is created from an existing object, as a copy of the existing object. The assignment operator is called when an already initialized object is assigned a new value from another existing object.

Which is correct for copy constructor?

8. What is the syntax of copy constructor? Explanation: The syntax must contain the class name first, followed by the classname as type and &object within parenthesis. Then comes the constructor body.

What are copy and move constructors C++?

What is a Move Constructor? The copy constructors in C++ work with the l-value references and copy semantics(copy semantics means copying the actual data of the object to another object rather than making another object to point the already existing object in the heap).


4 Answers

Usually it will be neither due to RVO.

If that optimisation can't be performed, then it will be a move, because the object being returned is going out of scope (and will be destroyed just after). If it can't be moved, then it will be copied. If it can't be copied, it won't compile.

The whole point of move constructors is that when a copy is going to be made of an object that is just about to be destroyed, it is often unnecessary to make a whole copy, and the resources can be moved from the dying object to the object being created instead.

You can tell when either the copy or move constructor is going to be called based on what is about to happen to the object being moved/copied. Is it about to go out of scope and be destructed? If so, the move constructor will be called. If not, the copy constructor.

Naturally, this means you may have both a move constructor and copy constructor in the same class. You can also have a copy assignment operator and a move assignment operator as well.

Update: It may be unclear as to exactly when the move constructor/assignment operator is called versus the plain copy constructor/assignment operator. If I understand correctly, the move constructor is called if an object is initialised with an xvalue (eXpiring value). §3.10.1 of the standard says

An xvalue (an “eXpiring” value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for example). An xvalue is the result of certain kinds of expressions involving rvalue references (8.3.2). [ Example: The result of calling a function whose return type is an rvalue reference is an xvalue. —end example ]

And the beginning of §5 of the standard says:

[ Note: An expression is an xvalue if it is:

  • the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type,
  • a cast to an rvalue reference to object type,
  • a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or
  • a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.

In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not. —end note ]


As an example, if NRVO can be done, it's like this:

void MoveAFoo(Foo* f) {
    new (f) Foo;
}

Foo myfoo; // pretend this isn't default constructed
MoveAFoo(&myfoo);

If NRVO can't be done but Foo is moveable, then your example is a little like this:

void MoveAFoo(Foo* fparam) {
    Foo f;

    new (fparam) Foo(std::move(f));
}

Foo f; // pretend this isn't being default constructed
MoveAFoo(&f);

And if it can't be moved but it can be copied, then it's like this

void MoveAFoo(Foo* fparam) {
    Foo f;

    new (fparam) Foo((Foo&)f);
}

Foo f; // pretend this isn't default constructed
MoveAFoo(&f);
like image 178
Seth Carnegie Avatar answered Oct 07 '22 21:10

Seth Carnegie


To back up @Seth, here's the relevant paragraph from the standard:

§12.8 [class.copy] p32

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

like image 45
Xeo Avatar answered Oct 07 '22 20:10

Xeo


The "disambiguation" is just your old friend, overload resolution:

Foo y;

Foo x(y);            // copy
Foo x(std::move(y)); // move

The expression y in the first example is an lvalue of type Foo, which binds to Foo const & (and also Foo & if you have such a constructor); the type of the expression std::move(y) in the second example is Foo &&, so it'll bind to Foo && (and also Foo const & absent the former).

In your example, the result of MoveAFoo() is a temporary of type Foo, so it'll bind to the Foo &&-constructor if one is available, and to a const-copy constructor otherwise.

Finally, in a function returning Foo (by value), the statement return x; is equivalent to return std::move(x); if x is a local variable of type Foo -- this is a special new rule to make the use of move semantics easier.

like image 22
Kerrek SB Avatar answered Oct 07 '22 19:10

Kerrek SB


Foo MoveAFoo() {
  Foo f;
  return f;
}

This is definition of function MoveAFoo that returns object of type Foo. In its body local Foo f; is created and destructed when it goes out of its scope.

In this code:

Foo x = MoveAFoo();

object Foo f; is created inside of MoveAFoo function and directly assigned into x, which means that copy constructor is not called.

But in this code:

Foo x;
x = MoveAFoo();

object Foo f; is created inside of MoveAFoo function, then the copy of f is created and stored into x and original f is destructed.

like image 2
LihO Avatar answered Oct 07 '22 20:10

LihO