c ++用移动而不是复制来累积
Posted
技术标签:
【中文标题】c ++用移动而不是复制来累积【英文标题】:c++ accumulate with move instead of copy 【发布时间】:2012-12-05 13:56:29 【问题描述】:我有以下代码
auto adder = [](string& s1, const string& s2)->string&&
if (!s1.empty())
s1 += " ";
s1 += s2;
return move(s1);
;
string test;
test.reserve(wordArray.size() * 10);
string words = accumulate(wordArray.begin(), wordArray.end(),
move(test), adder);
我想在这里避免字符串复制。不幸的是,这不是通过累积的 vs2012 实现来实现的。内部累积调用另一个函数 _Accumulate 并且右值功能在此过程中丢失。
我改为这样调用 _Accumulate 函数
string words = _Accumulate(wordArray.begin(), wordArray.end(),
move(test), adder);
我得到了预期的性能提升。
是否必须重写 std 库以考虑右值参数?
有没有其他方法可以让我使用累积来完成我想要的,而不会作弊太多?
【问题讨论】:
您的test
不是右值。 lambda 中的 s1
不是右值
C++20 将操作指定为acc = move(acc) + rhs
,这可以大大减少复制成本不高的类型的累积。例如,一个好的std::string
实现将有一个operator+(string&& lhs, T)
劫持lhs
,附加到它,然后返回它(这是RVOable)。
【参考方案1】:
查看最近发布的一篇 C++11 草案(N3337.pdf)我们可以看到 std::accumulate 的效果被指定为
通过使用初始值 init 初始化累加器 acc 来计算其结果,然后 对范围内的每个迭代器 i 使用 acc = acc + *i 或 acc = binary_op(acc, *i) 对其进行修改 [first,last) 按顺序排列。
因此,标准实际上禁止使用 std::move 作为旧累加器值的实现,如下所示:
template <class InputIterator, class T, class BinOp>
T accumulate (InputIterator first, InputIterator last, T init, BinOp binop)
while (first!=last)
init = binop(std::move(init), *first);
++first;
return init;
这对你来说很不幸。
选项 (1):自己实施这种移动感知累积。
选项(2):继续使用仿函数
struct mutating_string_adder
string operator()(string const& a, string const& b) const return a+b;
string operator()(string & a, string const& b) const a += b; return std::move(a);
string operator()(string && a, string const& b) const a += b; return std::move(a);
;
请注意,我没有在这里使用右值引用返回类型。这是有意的,因为它可能会避免悬空引用问题,例如在拾取最后一个重载并将“a”初始化为引用临时对象的情况下。字符串的所有 operator+ 重载也有意按值返回。
除此之外,您可能希望将 std::copy 与 std::stringstream 和输出流迭代器结合使用。
附录:备用mutating_string_adder
部分完美转发:
struct mutating_string_adder
template<class T, class U>
std::string operator()(T && a, U && b) const
return std::move(a) + std::forward<U>(b);
;
【讨论】:
mutating_string_adder
- 第二个 operator(),a 是左值,因此 move 不做任何事情。但是,我不确定第一个 operator() 是否应该使用 move。
@BЈовић:std::move 的目的是将左值转换为右值。所以,当然,std::move 做了一些事情。如果我在 return 语句中删除了 a 周围的 std::move,则返回值将是复制构造的,而不是 move 构造的。第一次重载不需要使用 std::move ,因为 a+b 已经是一个右值。
移动感知累积运行良好,没有任何副本,并且一直使用相同的保留区域
第一个是正确的,但我认为你对第二个是错误的。为什么要把左值变成右值?这是可能的吗,传递给 s1 的对象将被移动,并且不再存在。因此,访问它将是 UB
@BЈовић:我已经说过为什么在第二个 operator() 重载中使用 std::move 。抱歉,我没有看到任何问题。您认为在哪里可以访问已移动对象 - 除了可移动感知版本的累积分配?以上是关于c ++用移动而不是复制来累积的主要内容,如果未能解决你的问题,请参考以下文章