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 Assert
s 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 Assert
s instead of just empty std::string
s, 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