在转向C ++ 11后,我现在系统地在构造函数中按值传递我的字符串。但是现在,我意识到当使用构造函数体中的值时,它更容易引入错误:
class A(std::string val):
_val(std::move(val))
{
std::cout << val << std::endl; // Bug!!!
}
我可以做些什么来减少错误的机会?
答案 0 :(得分: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*?
};
即,写一个只能通过move
读取的线性类型,然后读取Assert
或抛出。
然后修改你的构造函数:
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<>
个包装器参数。
如果您的测试涵盖了所有代码路径,那么即使您从{Assert
多次std::string
move
,您也会得到很好的read_once
而不是空{{1}} s。 1}}变量。
答案 1 :(得分: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 !!!
}