在转向 C++11 之后,我现在系统地在构造函数中按值传递字符串。但是现在,我意识到在构造函数的主体中也使用值时更容易引入错误:
class A(std::string val):
_val(std::move(val))
{
std::cout << val << std::endl; // Bug!!!
}
我能做些什么来减少出错的机会?
在转向 C++11 之后,我现在系统地在构造函数中按值传递字符串。但是现在,我意识到在构造函数的主体中也使用值时更容易引入错误:
class A(std::string val):
_val(std::move(val))
{
std::cout << val << std::endl; // Bug!!!
}
我能做些什么来减少出错的机会?
至少在构造函数的实现中,以某种独特的方式从其目的中移出的名称参数
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
,然后读取Assert
s 或 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 Assert
s 而不仅仅是 empty std::string
s,即使您move
不止一次地从read_once
变量中去。
“在迁移到 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 !!!
}