I am reviewing some code like this, where A
is a moveable type:
// Returns true exactly when ownership of a is taken bool MaybeConsume(A&& a) { if (some condition) { Consume(std::move(a)); // ??? return true; } return false; } // ... elsewhere ... A a; if (!MaybeConsume(std::move(a))) { a.DoSomething(); // !!! }
Our static analysis tool complains that a
is used after being moved (at !!!
). IIUC std::move
is only a static_cast
, and the object a
won't actually get gutted until a move constructor or assignment operator is called (presumably in Consume
). Assuming MaybeConsume
satisfies the contract in the comment,
std::move
at ???
a no-op?(Probably this particular instance can be refactored to avoid the subtlety, but I would still like to ask for my own understanding).
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.
std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.
std::move itself does "nothing" - it has zero side effects. It just signals to the compiler that the programmer doesn't care what happens to that object any more. i.e. it gives permission to other parts of the software to move from the object, but it doesn't require that it be moved.
std::move in C++Moves the elements in the range [first,last] into the range beginning at result. The value of the elements in the [first,last] is transferred to the elements pointed by result. After the call, the elements in the range [first,last] are left in an unspecified but valid state.
It's a spurious warning from your static analysis tool.
- Does this work?
Yes, MaybeConsume
is doing what the comment says. It's only taking ownership of its argument when some condition
is true (assuming Consume
actually does move construct/assign from its argument).
std::move
is indeed just a fancy static_cast<T&&>
so MaybeConsume(std::move(a))
is not transferring ownership, you're simply binding a reference to MaybeConsume
's parameter.
- Is it UB?
No, you're not making use of a
if MaybeConsume
indicates it has assumed ownership of its argument.
- Is
std::move
at ??? a no-op?
Well, it's a no-op because it's just a static_cast
, but if you meant to ask whether it's unnecessary, then, no, it isn't. Within the body of MaybeConsume
, a
is an lvalue because it has a name. If the signature of Consume
is void Consume(A&&)
, then the code won't compile without that std::move
.
From the example usage you've shown, it seems you're not supposed to call MaybeConsume
with a prvalue argument, since the caller should presumably use the argument in some other manner if the function returns false
. If that's true, then you should change its signature to bool MaybeConsume(A&)
. This will probably make your static analysis tool happy because that would allow you to write if (!MaybeConsume(a))
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With