考虑以下函数 accept
类型的 "通用引用"。T
并将其转发给 parse<T>()
函数对象,其中lvalues和rvalues分别有一个重载。
template<class T>
void accept(T&& arg)
{
parse<T>()(std::forward<T>(arg), 0); // copy or move, depending on rvaluedness of arg
}
template<class T>
class parse
{
// 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 */ }
};
因为完美转发会使源对象处于有效但未定义的状态, 所以不可能在同一范围内再次完美转发. 下面我的尝试是在一个假设的情况下,尽可能少的拷贝 split()
函数,它以一个 int
表示必须对输入数据进行的次数。
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
}
问题问:这是否是对同一数据进行多次传递的完美转发的推荐方式?如果不是,有什么更习惯的方法来减少副本的数量?
问题:请问这是对同一数据的多次传递应用完美转发的推荐方式吗? 这是对同一数据进行多次传递时应用完美转发的推荐方式吗?
是的,当你需要多次传递数据时,这是应用完美转发(或移动)的推荐方式。 只有在你最后一次访问时才(可能)从中移动。 事实上,这种情况在《》中已经预见到了。原招,并且是 很 的原因,用rvalue-reference类型声明的 "命名 "变量不会被隐式地从。 从 N1377:
尽管命名的rvalue引用可以绑定到rvalue上,但在使用时,会被视为lvalue。比如说:虽然一个r值可以绑定到f()的 "a "参数上,但是一旦绑定,a就会被视为l值。
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&)
}
虽然rvalue可以绑定到f()的 "a "参数上,但一旦绑定,a就会被视为lvalue。特别是,对重载函数g()和h()的调用解析为const A&(lvalue)重载。 在f中把 "a "作为r值处理,会导致容易出错的代码。 首先,g()的 "移动版本 "会被调用,这很可能会窃取 "a",然后被窃取的 "a "会被发送到h()的移动重载。
如果你想让 "移动版 "的g()被调用,那么很可能会盗取 "a",然后盗取的 "a "会被发送到h()的移动重载中。h(a)
在上面的例子中,你必须明确地这样做。
h(std::move(a)); // calls h(A&&);
As 凯西 在评论中指出,你在传递l值时有一个重载问题。
#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
与lvalue引用类型,是为了部分特殊化解析。
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&>
如果需要的话。
(我知道,这是个老话题)
正如评论中所说,数据是一个大数组或uint64_t的向量。比参数传递更好的优化是防止最后的复制,可能是将许多复制操作优化为
一步完成,而不是许多独立的副本。
一个起点可以是这样的 更快地替代memcpy? 的答案,其中包括类似memcpy的代码。你将不得不乘以写到目标的代码行来代替写几份数据。
你也可以将memset和memcpy结合在一起,memset是为重复向内存中写入相同的值而优化的,而memcpy则是为每个内存块读写一次而优化的。你可以在这里看看优化后的源码。https:/github.comKNNSpeedAVX-Memmove。
最好的代码将是特定的架构和处理器使用。所以你必须测试和比较你所达到的速度。