问题:这是对同一数据的多次传递应用完美转发的推荐方法吗?
是的,当您需要多次传递数据时,这是应用完美转发(或移动)的推荐方式。只有(可能)在您最后一次访问时离开它。事实上,这种情况在最初的 move 论文中已经预见到了,这正是使用 rvalue-reference 类型声明的“命名”变量没有被隐式移动的原因。从N1377 开始:
尽管命名的右值引用可以绑定到右值,但在使用时它们被视为左值。例如:
struct A {};
void h(const A&);
void h(A&&);
void g(const A&);
void g(A&&);
void f(A&& a)
{
g(a); // calls g(const A&)
h(a); // calls h(const A&)
}
虽然右值可以绑定到 f() 的“a”参数,但一旦绑定,a 现在被视为左值。特别是,对重载函数 g() 和 h() 的调用解析为 const A& (lvalue) 重载。将“a”视为 f 中的右值会导致容易出错的代码:首先调用 g() 的“移动版本”,这可能会窃取“a”,然后将窃取的“a”发送到移动 h() 的重载。
如果你想h(a)
在上面的例子中移动,你必须明确地这样做:
h(std::move(a)); // calls h(A&&);
正如凯西在评论中指出的那样,传入左值时会遇到重载问题:
#include <utility>
#include <type_traits>
template<class T>
class parse
{
static_assert(!std::is_lvalue_reference<T>::value,
"parse: T can not be an lvalue-reference type");
public:
// parse will modify a local copy or move of its input parameter
void operator()(T const& arg, int n) const { /* optimized for lvalues */ }
void operator()(T&& arg , int n) const { /* optimized for rvalues */ }
};
template<class T>
void split(T&& arg, int n)
{
typedef typename std::decay<T>::type Td;
for (auto i = 0; i < n - 1; ++i)
parse<Td>()(arg , i); // copy n-1 times
parse<Td>()(std::forward<T>(arg), n - 1); // possibly move the n-th time
}
上面我已经按照 Casey 的建议修复了它,parse<T>
只在非引用类型上使用std::decay
. 我还添加了一个 static_assert 以确保客户端不会意外犯此错误。这static_assert
不是绝对必要的,因为无论如何您都会收到编译时错误。但是,它static_assert
可以提供更易读的错误消息。
不过,这不是解决问题的唯一方法。另一种允许客户端parse
使用左值引用类型实例化的方法是部分专门化解析:
template<class T>
class parse<T&>
{
public:
// parse will modify a local copy or move of its input parameter
void operator()(T const& arg, int n) const { /* optimized for lvalues */ }
};
现在客户不需要decay
跳舞了:
template<class T>
void split(T&& arg, int n)
{
for (auto i = 0; i < n - 1; ++i)
parse<T>()(arg , i); // copy n-1 times
parse<T>()(std::forward<T>(arg), n - 1); // possibly move the n-th time
}
parse<T&>
如有必要,您可以应用特殊逻辑。