Copy ctor instead of moving ctor

Why is the copy constructor called when returning from bar instead of the move constructor?

 #include <iostream> using namespace std; class Alpha { public: Alpha() { cout << "ctor" << endl; } Alpha(Alpha &) { cout << "copy ctor" << endl; } Alpha(Alpha &&) { cout << "move ctor" << endl; } Alpha &operator=(Alpha &) { cout << "copy asgn op" << endl; } Alpha &operator=(Alpha &&) { cout << "move asgn op" << endl; } }; Alpha foo(Alpha a) { return a; // Move ctor is called (expected). } Alpha bar(Alpha &&a) { return a; // Copy ctor is called (unexpected). } int main() { Alpha a, b; a = foo(a); a = foo(Alpha()); a = bar(Alpha()); b = a; return 0; } 

If bar does return move(a) , then the behavior will be as expected. I don’t understand why the call to std::move needed if foo calls the returned move constructor.

+5
source share
4 answers

There are two things in this situation:

  • a in bar(Alpha &&a) is a named rvalue reference; therefore treated as an lvalue.
  • a is still a link.

Part 1

Since a in bar(Alpha &&a) is the named value of rvalue, it is treated as an lvalue. The motivation for handling named rvalue links as lvalues ​​is to provide security. Consider the following:

 Alpha bar(Alpha &&a) { baz(a); qux(a); return a; } 

If baz(a) is considered a as an rvalue, then the move constructor can be called, and qux(a) may be invalid. The standard avoids this problem by treating rvalue references as lvalues.

Part 2

Since a is still a reference (and may refer to an object outside the scope of bar ), bar calls the copy constructor on return. The motivation for this behavior is security.

References

+4
source

Yes, very confusing. I would like to give another SO post here implicite move . where I find the following comments somewhat convincing,

And so the standards committee decided that you should be explicit about moving for any named variable, regardless of its reference type

In fact, "& &" is already indicating a vacation and while you are "returning" it is safe enough to move.

perhaps this is just a selection from a standard committee.

paragraph 25 of “efficient modern C ++” from scott meyers also summed up this without giving much explanation.

 Alpha foo() { Alpha a return a; // RVO by decent compiler } Alpha foo(Alpha a) { return a; // implicit std::move by compiler } Alpha bar(Alpha &&a) { return a; // Copy ctor due to lvalue } Alpha bar(Alpha &&a) { return std:move(a); // has to be explicit by developer } 
+1
source

This is a very common mistake when people first learn about rvalue links. The main problem is the confusion between type and category of values .

int is a type. int& is a different type. int&& is another type. These are all different types.

lvalues ​​and rvalues ​​are things called value categories. Please check out the fantastic diagram here: What are rvalues, lvalues, xvalues, glvalues ​​and prvalues? . You can see that in addition to lvalues ​​and rvalues, we also have prvalues ​​and glvalues ​​and xvalues, and they form different relationships such as venn.

C ++ has rules that say that variables of various types can be associated with expressions. However, the reference type of expressions is discarded (people often say that expressions do not have a reference type). Instead, the expression has a category of values ​​that determines which variables can bind to it.

Set the other way: rvalue links and lvalue links are directly related only to the left side of the assignment, and the variable is created / bound. On the right side, we are talking about expressions, not variables, and the reference value rvalue / lvalue only matters in the context of defining a category of values.

A very simple example to start with is a simple look at things of pure int type. An int variable is an lvalue as an expression. However, the expression consisting in evaluating the function returning int is the value of r. This makes most people intuitive; the key point is the separation of the type of expression (even before dropping links) and its category of values.

The fact that even if variables of type int&& can only bind to rvalues ​​does not mean that all expressions of type int&& are equal to rvalues. In fact, since the rules at http://en.cppreference.com/w/cpp/language/value_category say that any expression consisting of a variable name is always an lval value regardless of type.

This is why you need std::move to pass rvalue links to subsequent functions that take an rvalue link. This is because rvalue links are not tied to other rvalue links. They are attached to rvalues. If you want to get a move constructor, you need to give it an rvalue for the binding, and the link named rvalue is not the value of r.

std::move is a function that returns an rvalue reference. And what is the value category of such an expression? Rvalue? Nope. This is the value of x. This is basically an rvalue, with some additional properties.

+1
source

In the expressions foo and bar expression a is the value of l. return a; means initialization of the object of the return value from the initializer a and returns this object.

The difference between the two cases is that overload resolution for this initialization is done differently depending on whether a is declared as a non-volatile automatic object inside the innermost enclosing block or function parameter.

What is this foo , but not bar . (In bar , a declared as a reference). Therefore, return a; in foo selects a move constructor to initialize the return value, but return a; in bar selects the copy constructor.

Full text: C ++ 14 [class.copy] / 32:

When the criteria for performing the copy / move operation are fulfilled, but not for declaring an exception, but the object to be copied is indicated by the value lvalue or when the expression in the return statement is (possibly in parentheses) an id expression that names the object with automatic storage time declared in body or parameter-declaration-sentence of the innermost closing function or lambda expression, the overload resolution for choosing the constructor for the copy is first performed as if the object were both valued by rvalue. If the first overload resolution fails or has not been executed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the type of the object (possibly cv-qualified), the overload resolution is executed again, treating the object as naming. [Note. This two-step overload resolution should be performed regardless of whether copying occurs. It defines the calling constructor if elision fails, and the selected constructor must be available, even if the call is canceled. -end note]

where "the criteria for performing the copy / move operation are fulfilled" refers to [class.copy] /31.1:

  • in a return expression in a function with the type of the return class, when the expression is the name of a non-volatile automatic object (except for the function or catch-clause parameter) with the same cv-unqualified type as the return type function, the copy / move operation can be omitted when building an automatic object directly in function returns value

Please note that these texts will be changed for C ++ 17.

+1
source

Source: https://habr.com/ru/post/1271084/


All Articles