如何优雅地就地修改容器中的所有元素?

5
#include <vector>

using namespace std;

class A
{
public:
    A() = default;

    void Add()
    {
        a++;
    }

private:
    int a;
};

int main()
{
    vector<A> x(10);
    for (auto pos = x.begin(); pos != x.end(); ++pos) pos->Add();
}
for_each似乎是非修改性的。http://en.cppreference.com/w/cpp/algorithm/for_each

f - 函数对象,应用于范围[first,last)中每个迭代器解引用的结果

函数的签名应等同于以下内容:

void fun(const Type &a);

签名不需要具有const&。类型Type必须是这样的,即InputIt类型的对象可以被取消引用,然后隐式转换为Type。

所以,我的问题是:

是否有标准函数/方法可以执行与for(auto pos = x.begin(); pos!= x.end(); ++pos)pos-&gt; Add(); 相同的操作?


10
实际上,“for_each”正在修改。 - cqdjyy01234
2
请阅读您链接的页面直到最后。那里有一个例子。 - n. m.
1
引用该页面的内容:如果InputIt是一个可变迭代器,f可以通过解引用的迭代器修改范围内的元素。 - Danh
4个回答

10

不确定您为什么这样写

for_each是非修改性函数

这个没问题:

for_each(begin(x), end(x), [](int &i){++i;});  

对于一个整数向量,例如:


5

您可以使用

for(auto& p : x) p.Add();

这段代码简洁优雅,同时也相当高效,因为它允许编译器直接查看操作,而不需要插入额外的逻辑。输入更少,编译更快,打字错误时不会出现冗长的无意义屏幕提示。
例如,对于g++生成的代码:
for (auto& y : x) {
    y++;
}

其中声明了一个 x,是一个 std::vector<int> 类型的变量,内部循环的代码如下:

.L8:
    movdqa  (%rdi,%rax), %xmm0
    addq    $1, %rdx
    paddd   %xmm1, %xmm0
    movaps  %xmm0, (%rdi,%rax)
    addq    $16, %rax
    cmpq    %rdx, %r8
    ja  .L8

每次迭代使用MMX指令递增4个整数。


对我来说,这是最好的方式。然而,原帖作者需要标准函数。 - Humam Helfawi
在我看过的许多基准测试中,for_eachfor(auto& p : x) 稍微快一些。 - Alex Lop.
@AlexLop:不知道你在说哪些测试。直接在引用上使用for可以让编译器直接看到所有内容。如果运气好,for_each也可以通过剥离所有机制并内联lambda函数来实现相同的效果...但没有理由认为它会更好。请参见编辑部分以查看生成代码的示例。 - 6502

3

std::for_each 是你正在寻找的东西:


该函数可以对一个范围内的元素执行指定操作。
std::for_each(std::begin(x),std::end(x),[](A& item){item.add();});

然而,6502的答案是更好的方法。

编辑:

关于你引用的那句话,在同一页中请注意:

f可以通过解引用的迭代器修改范围内的元素


3
晚来的回答,但是没有人提到 std::transform:
#include <algorithm>
#include <vector>
#include <iostream>

using namespace std;

class A
{
public:
    A() = default;

    A& Add()
    {
        a++;
        return *this; // this will make it compatible with std::transform
    }

    friend std::ostream& operator<<(std::ostream& os, A const& a)
    {
        return os << a.a;     
    }
private:
    int a;
};

int main()
{
    vector<A> x(10);
    std::transform(x.begin(), x.end(), x.begin(), [](auto& elem) { 
        return elem.Add(); 
    });
    std::copy(x.begin(), x.end(), std::ostream_iterator<A>(std::cout, ","));
}

实时例子。

请注意,我必须修改您的Add()的返回类型为A&。这使其与std::transform兼容,并模仿了operator++的返回类型。

当然,使用单个语句的原始循环或for_each也可以。但是,transform是一个词汇表算法,立即向您的代码读者表示您正在修改容器。对于原始循环和for_each,这需要更仔细的审查(在这里可能是微不足道的,但在更大的代码中,这些事情会累积起来)。例如,请注意以下差异:

    std::transform(x.begin(), x.end(), x.begin(), [](auto& elem) { 
        // many lines but x will always be written to
    });

    std::for_each(x.begin(), x.end(), [](auto& elem) { 
        // many lines, will always need to inspect the code here 
        // to know if `elem` is actually being written to
        // the `auto&` is only a hint that this might happen
    });

这样做是否会导致不必要的复制? - Mateen Ulhaq
@MateenUlhaq 不行,因为 elem 是按引用传递的。 - TemplateRex
@TemplateRex:是的,因为std::transform将会接受lambda返回的A&并将其重新赋值给从x.begin()开始的范围。基本上,对于x中的每个elt,这相当于执行了elt = elt.Add();,而我们实际上只需要elt.Add();。你为这些不必要的operator=调用付出了代价。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接