5

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

class A(std::string val):
  _val(std::move(val))
{
  std::cout << val << std::endl; // Bug!!!
}

我能做些什么来减少出错的机会?

4

2 回答 2

2

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

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

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

如果您有这么长的构造列表,您可能会错过其中的两种用途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*?
};

即,编写一个只能从 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
}

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

如果您的测试涵盖了所有代码路径,您将得到 nice Asserts 而不仅仅是 empty std::strings,即使您move不止一次地从read_once变量中去。

于 2013-07-03T14:28:16.863 回答
0

“在迁移到 C++11 之后,我现在系统地在构造函数中按值传递字符串。”

对不起,我不明白为什么要这样做。与传统方法相比,这提供了哪些改进?(这基本上是错误证明)。

class A(const std::string & s):
  _val(s)
{
  std::cout << s << std::endl; // no Bug!!!
  std::cout << _val << std::endl; // no Bug either !!!
}

于 2013-07-03T16:29:56.723 回答