Why is this an issue?
Usually, when copying an object, the source object is unchanged, which means that all resources owned by the source objects have to be duplicated
during the copy operation. In the case that the source object will no longer be used, this duplication is not efficient. Since C++11, a mechanism
named move semantic has been added to detect such cases and replace the expensive copy by a much cheaper move operation that will steal resources.
The cornerstone of move semantic is the ability to detect during a "copy" if the source object will be reused or not. There are three
situations:
- The object is a temporary object, with no name, and if it can’t be named, it can’t be used
- The object is used in some specific places, such as a return statement
- The user explicitly promises to the compiler that he won’t care for the current value of the object any longer. He does so by using the
specific cast operation named
std::move
.
If the user write std::move
in one situation that is already handled by the first two cases, it has two drawbacks:
- It is clumsy, useless code, which make understanding the code more complex
- In some cases, it can decrease performances, because this can deactivate another optimization of the compiler, named copy elision.
When copy elision occurs, the object is neither copied nor moved (even if the copy/move constructors have side effects), in fact, the two objects
are collapsed into only one memory location. When copy elision occurs is compiler-dependent, but is mandatory in the following cases:
- in a return statement if the returned object is a prvalue of the same class type as the function return type
- in the initialization of a variable if the initializer expression is a prvalue of the same class type as the variable type
This rule reports an issue when the use of std::move
prevents the copy elision from happening.
Noncompliant code example
class A {};
A getA();
A f() {
A a = std::move(getA()); // Noncompliant, prevents copy elision
vector<A> v;
v.push_back(std::move(getA())); // Noncompliant
return std::move(a); // Noncompliant, prevents copy elision
}
Compliant solution
class A {};
void test(A a);
A f() {
A a = getA(); // Compliant
vector<A> v;
v.push_back(getA()); // Compliant
return a; // Compliant
}
Resources