Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mistakenly using a moved value

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?

like image 410
small_duck Avatar asked Nov 02 '22 19:11

small_duck


1 Answers

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.

like image 191
Yakk - Adam Nevraumont Avatar answered Nov 15 '22 05:11

Yakk - Adam Nevraumont