I can’t speek to Wpilib specifically, though can describe the behavior of std::move
. This templated function performs a “cast to r-value”. A brief summary of an “r value” is that it is either a newly constructed object or one which we have indicated we can “move from”.
An object can be passed in a few different ways in C++ (this list is non-exhaustive):
By value: void f(MyObject o) {}
By reference: void f(MyObject& o) {}
By rvalue reference: void f(MyObject&& o) {}
When you pass an object by value, the function will receive a copy of the object which it owns: This could be a copy of something you passed in, or if you called it such as f(MyObject{})
, no copy will be made and the ownership simply transferred to the function.
When passing by reference, no copy is made and the ownership refers to the object passed in.
Passing by rvalue reference is similar to passing by reference, but with the distinction that it will only bind to an rvalue reference. On it’s own, passing by rvalue reference doesn’t actually move anything. But it indicates that the callee is free to move from the object if it so chooses.
Some objects cannot be easily copied - there sometimes doesn’t exist logic to sensibly have two copies of the same “thing”. C++ still allows us to pass these around - an object that has a deleted copy constructor can still have a valid move constructor. The move constructor takes an rvalue reference. In order to call a constructor which takes an rvalue reference, we must pass it an object which can bind to an rvalue reference, such as a new instance f(MyObject{})
. Alternatively, if we already have an instance which we would like to pass in, we can use std::move
. This performs a cast to rvalue reference, which means our lvalue is now an rvalue. So we’ve called the move constructor. This is typically designed to destroy the contents of the passed in object in order to move the internal state of the passed in object to the new object. So the object my_object
we passed to f(std::move(my_object))
contains a valid but indeterminate state, typically some form of “empty”. For instance, a moved from vector
often has size 0, and a move from unique_ptr
is nullptr
.
To answer your questions:
-
std::move
is required because the command doesn’t have a copy constructor, only a move constructor. When adding a command, by wrapping your object in std::move
you indicate you don’t need it any more and the callee can take logical ownership of its state. The command object you still have is now “empty”. (I’m not sure how wpilib defines this, but you should assume it’s garbage and should either be carefully reset or not reused at all).
-
If you move a command group into another command with std::move
, the original command object is likely “empty”. So you cannot reuse the same instance.
Now, I’m not that familiar with the wpilib api, but if you really wanted to, you *might* be able to use a shared_ptr
to share a command’s ownership. But this is usually considered a code smell. I’ll let @calcmogul comment on this.