高效积累

tac*_*ach 7 c++ algorithm rvalue-reference accumulate c++11

假设我有字符串向量,我想通过std :: accumulate连接它们.

如果我使用以下代码:

std::vector<std::string> foo{"foo","bar"};
string res=""; 
res=std::accumulate(foo.begin(),foo.end(),res,
  [](string &rs,string &arg){ return rs+arg; });
Run Code Online (Sandbox Code Playgroud)

我可以肯定会有临时对象构造.

这个答案中,他们说std :: accumulate的效果是这样指定的:

通过初始化累加器acc初始值init计算其结果,然后按顺序对[= first,last]范围内的每个迭代器i用acc = acc +*i或acc = binary_op(acc,*i)修改它.

所以我想知道这样做的正确方法是什么,以避免不必要的临时对象构造.

一个想法是以这种方式改变lambda:

[](string &rs,string &arg){ rs+=arg; return rs; }
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我认为我强制有效串联字符串并帮助编译器(我知道我不应该)省略不必要的副本,因为这应该等同于(伪代码):

accum = [](& accum,& arg){ ...; return accum; }
Run Code Online (Sandbox Code Playgroud)

因此

accum = & accum;
Run Code Online (Sandbox Code Playgroud)

另一个想法是使用

accum = [](& accum,& arg){ ...; return std::move(accum); }
Run Code Online (Sandbox Code Playgroud)

但这可能会导致类似于:

accum = std::move(& accum);
Run Code Online (Sandbox Code Playgroud)

这看起来很可疑.

写这个的正确方法是什么,以尽量减少不必要的临时对象创建的风险?我不仅对std :: string感兴趣,我很乐意有一个解决方案,这可能适用于任何具有复制和移动构造函数/赋值的对象.

Dav*_*eas 11

我会将其分解为两个操作,首先std::accumulate获取需要创建的字符串的总长度,然后std::for_each使用更新本地字符串的lambda:

std::string::size_type total = std::accumulate(foo.begin(), foo.end(), 0u, 
                [](std::string::size_type c, std::string const& s) {
                    return c+s.size() 
                });
std::string result;
result.reserve(total);
std::for_each(foo.begin(), foo.end(), 
              [&](std::string const& s) { result += s; });
Run Code Online (Sandbox Code Playgroud)

对此的常见替代方法是使用表达式模板,但这不适合答案.基本上,您创建一个映射操作的数据结构,但不执行它们.最终评估表达式时,它可以预先收集所需的信息,并使用它来保留空间并执行复制.使用表达式模板的代码更好,但更复杂.

  • @tach:你可以选择你想要的行为或你使用的工具,但不能用锤子拧开. - 虽然这不完全正确,如果你愿意付出足够的努力,你可以创建基础设施来做到这一点(同样,表达模板类型的方法) (4认同)

Adi*_*vit 5

std::accumulate没有任何冗余副本的情况下有效使用并不明显。
除了被重新分配和传入和传出 lambda 之外,累积值可能会被实现在内部复制。
另请注意,std::accumulate()它本身采用初始值by-value,调用 copy-ctor ,从而忽略reserve()对副本源所做的任何s (如其他一些答案中所建议的那样)。

我发现连接字符串的最有效方法如下:

std::vector<std::string> str_vec{"foo","bar"};

// get reserve size:
auto sz = std::accumulate(str_vec.cbegin(), str_vec.cend(), std::string::size_type(0), [](int sz, auto const& str) { return sz + str.size() + 1; });

std::string res;
res.reserve(sz);
std::accumulate(str_vec.cbegin(), str_vec.cend(),
   std::ref(res), // use a ref wrapper to keep same object with capacity
   [](std::string& a, std::string const& b) -> std::string& // must specify return type because cannot return `std::reference_wrapper<std::string>`.
{                                                           // can't use `auto&` args for the same reason
   a += b;
   return a;
});
Run Code Online (Sandbox Code Playgroud)

结果将在res.
此实现没有冗余副本、移动或重新分配。


Vla*_*cow 4

尝试以下操作

res=std::accumulate(foo.begin(),foo.end(),res,
  [](string &rs, const string &arg) -> string & { return rs+=arg; });
Run Code Online (Sandbox Code Playgroud)

在这呼唤之前也许有一种呼唤的意义

std::string::size_type n = std::accumulate( foo.begin(), foo.end(), 
   std::string::size_type( 0 ),
   [] ( std::string_size_type n, const std::string &s ) { return ( n += s.size() ); } );

res.reserve( n );
Run Code Online (Sandbox Code Playgroud)

  • 没有任何应对办法。复制赋值运算符会看到字符串尝试分配给自身。 (2认同)