Having made the move to C++11, I am now systematically passing my strings by value in my constructors. But now, I realize that it makes it easier to introduce bugs when also using the value in the body of the constructor:
class A(std::string val):
_val(std::move(val))
{
std::cout << val << std::endl; // Bug!!!
}
What can I do to reduce the chances of getting it wrong?
Name arguments whose purpose is to be moved-from in some distinctive manner, at least within the implementation of the constructor
A::A(std::string val_moved_from):
_val(std::move(val_moved_from))
{
std::cout << val_moved_from << std::endl; // Bug, but obvious
}
then move from them as early as possible (in the construction list, say).
If you have such a long construction list you can miss two uses of val_moved_from in it, this doesn't help.
An alternative would be to write up a proposal to fix this problem. Say, extend C++ so that the types or scopes of local variables can be changed by operations on them, so std::safe_move(X) both moves from X and marks X as an expired variable, no longer valid to use, for the remainder of its scope. Working out what to do when a variable is half-expired (expired in one branch, but not in another) is an interesting question.
Because that is insane, we can instead attack it as a library problem. To a certain limited extent, we can fake those kind of tricks (a variable whose type changes) at run time. This is crude, but gives the idea:
template<typename T>
struct read_once : std::tr2::optional<T> {
template<typename U, typename=typename std::enable_if<std::is_convertible<U&&, T>::value>::type>
read_once( U&& u ):std::tr2::optional<T>(std::forward<U>(u)) {}
T move() && {
Assert( *this );
T retval = std::move(**this);
*this = std::tr2::none_t;
return retval;
}
// block operator*?
};
ie, write a linear type that can only be read from via move, and after that time reading Asserts or throws.
Then modify your constructor:
A::A( read_once<std::string> val ):
_val( val.move() )
{
std::cout << val << std::endl; // does not compile
std::cout << val.move() << std::endl; // compiles, but asserts or throws
}
with forwarding constructors, you can expose a less ridiculous interface with no read_once types, then forward your constructors to your "safe" (possibly private) versions with read_once<> wrappers around the arguments.
If your tests cover all code paths, you'll get nice Asserts instead of just empty std::strings, even if you go and move more than once from your read_once 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