错误地使用移动的值

sma*_*uck 5 c++ move-semantics c++11

转移到C++ 11后,我现在系统地在构造函数中按值传递我的字符串.但是现在,我意识到当使用构造函数体中的值时,它更容易引入错误:

class A(std::string val):
  _val(std::move(val))
{
  std::cout << val << std::endl; // Bug!!!
}
Run Code Online (Sandbox Code Playgroud)

我该怎么做才能减少弄错的可能性?

Yak*_*ont 2

名称参数的目的是以某种独特的方式移出,至少在构造函数的实现中

A::A(std::string val_moved_from):
 _val(std::move(val_moved_from))
{
  std::cout << val_moved_from << std::endl; // Bug, but obvious
}
Run Code Online (Sandbox Code Playgroud)

然后尽早离开它们(例如在施工清单中)。

如果你有这么长的构造列表,你可能会错过val_moved_from其中的两个用途,这没有帮助。

另一种方法是编写一份解决此问题的提案。比如说,扩展 C++,以便可以通过对局部变量的操作来更改局部变量的类型或作用域,因此std::safe_move(X)两者都会从过期变量中移出X并标记X为过期变量,在其作用域的剩余部分中不再有效使用。弄清楚当变量半过期(在一个分支中过期,但在另一个分支中未过期)时该怎么办是一个有趣的问题。

因为这太疯狂了,我们可以把它当作一个库问题来攻击。在一定程度上,我们可以在运行时伪造这些技巧(类型发生变化的变量)。这很粗糙,但给出了一个想法:

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*?
};
Run Code Online (Sandbox Code Playgroud)

即,编写一个只能从 via 读取的线性类型move,然后读取Asserts 或 throws。

然后修改你的构造函数:

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
}
Run Code Online (Sandbox Code Playgroud)

使用转发构造函数,您可以公开一个不那么荒谬的没有read_once类型的接口,然后将构造函数转发到带有参数包装器的“安全”(可能private)版本。read_once<>

如果您的测试覆盖了所有代码路径,即使您多次从变量中进行和操作,您也会得到很好的Assert而不只是空的。std::stringmoveread_once

  • 但你必须记住要做这一切。如果你能记住这一点,那么你一开始就不会犯这个错误。 (2认同)