I find these mistake to be all-too-easy for developers to make. Is there a best practice or authoritative way to avoid this? Is there a compiler flag or hint that works across multiple platforms?
We recommend rewriting such code in a cleaner way. The check is path sensitive, so it will follow the flow of execution and avoid warning on code like the one below. In our analysis, we basically track what’s happening with the objects. If we reassign a moved-from object it is no longer moved from.
The use after move check is intended to find exactly such code, when we are using a moved-from object in a possibly unintended way. There are several interesting things happening in the above example: std::move does not actually move m. It’s only cast to a rvalue reference.
The use after move check is intended to find exactly such code, when we are using a moved-from object in a possibly unintended way. There are several interesting things happening in the above example: std::move does not actually move m.
Calling consume will move the internal representation of m. According to the standard, the move constructor must leave m in a valid state so it can be safely destroyed. However, we can’t rely on what that state is.
An effective rule of thumb: Never use std::move
nor std::forward
and never type cast to an rvalue (or universal) reference. If you never move from a variable or a reference, then you can't make the mistake of using it after. This approach obviously has a drawback, since those utilities are useful in some cases for turning a copy into a move when the move is sufficient; not to mention the cases where it is necessary.
Approach for your own types: Add assertions into member functions that verify whether the instance has been moved from, and rely on them to trigger during testing. The "moved" state will need to be stored as a member. Assertions and the member can be removed in release build. A drawback is that this adds otherwise unnecessary boilerplate to every member function.
An extra approach: Use a static analysis tool that attempts to detect the potential mistake.
A reasonable rule of thumb: Keep your functions short. When the function is short, a use will be close to the move, and thus the potential mistake is easier to spot.
So the issue really is "read" after move. I think I agree that any use of std::move should be code reviewed as a potential risk. If the std::move is at the end of a function with a local value or value parameter, all good.
Anything else needs scrutiny, and any use of the variable after the move should be given attention. I guess giving the variable an "_movable" suffix will also help with the code review.
The few cases of write after move, such as swap will just have to be defended during the review.
Personally, I still treat std::move as a smell in the code, much like casts.
I'm not sure of any lint rules that would enforce this pattern, but I'm sure they are easy to write :-)
To ban std::move
is short-sighted. The best way to avoid read-after-move is to std::move
at the end of the scope that introduced the moved-from variables.
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