通过对输入参数的多次传递实现完美转发
Perfect forwarding with multiple passes over input arguments
考虑以下函数accept
,该函数接受类型为T
的"通用引用",并将其转发给具有左值重载和右值重载的parse<T>()
函数对象:
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
}
问题:这是对同一数据的多次传递应用完美转发的推荐方法吗?如果没有,有什么更惯用的方法可以最大限度地减少副本数量?
问题:这是对同一数据的多次传递应用完美转发的推荐方法吗?
是的,当您需要多次传递数据时,这是应用完美转发(或移动)的推荐方法。只有在您最后一次访问时才(可能)离开它。事实上,这种情况在最初的移动论文中就已经预见到了,这正是用类型右值引用声明的"命名"变量没有被隐式移动的原因。来自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"参数,但一旦绑定现在被视为左值。特别是,对重载的调用函数g()和h()解析为常量A&(左值)过载。将"a"视为f中的右值会导致容易出错的代码:首先,将调用g()的"移动版本",这可能盗窃"a",然后被盗窃的"a"将被送往移动h()的过载。
如果你想让h(a)
在上面的例子中移动,你必须明确地这样做:
h(std::move(a)); // calls h(A&&);
正如Casey在评论中指出的那样,在传递lvalues:时会出现过载问题
#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的建议修复了它,通过使用std::decay
仅在非引用类型上实例化parse<T>
。我还添加了一个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&>
下应用特殊逻辑。
(我知道,这是一个旧线程)
如注释中所述,数据是uint64_t的大数组或向量。与防止最终拷贝的参数传递相比,更好的优化可能是优化的许多拷贝操作
- 读取一次
- 写多次(每次预定传球)
在一个步骤中,而不是在多个独立副本中。
一个起点可能是这种更快的memcpy替代品?它的答案包括类似memcpy的代码。您必须将写入目标的代码行相乘,才能写入数据的多个副本。
您还可以组合memset和memcpy,前者经过优化,可以一次又一次地将相同的值写入内存,后者经过优化,每个块可以读取和写入内存块一次。您可以在此处查看优化的源代码:https://github.com/KNNSpeed/AVX-Memmove
最佳代码将特定于所使用的体系结构和处理器。因此,你必须测试和比较你所达到的速度。
- 如果没有malloc,链表实现将失败
- 如何在c++中实现处理器调度模拟器
- 试试完美的正方形,你能给点小费吗
- 如何在c++中使用引用实现类似python的行为
- 实现无开销push_back的最佳方法是什么
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- 实现一个在集合上迭代的模板函数
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 如何正确实现和访问运算符的各种自定义枚举器
- C++Union/Struct位域的实现和可移植性
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 无需使用 ODR 即可实现完美转发
- 为什么完美的转发(全部捕获)不能实现复制分配?
- 实现一个完美转发到std::thread的函数
- 通过对输入参数的多次传递实现完美转发
- 使用移动语义和完美转发来实现"懒惰"运算符+
- 可以实现完美的哈希函数
- 如何在非泛型类型上实现完美转发
- 如何模板化c++构造函数以实现完美的转发