使用for_each修改std容器(即使你不应该)

时间:2017-01-25 12:20:37

标签: c++ stl

我正在参加C ++的自学课程,学习标准库的工作方式,我想了解这个使用for_each的代码是如何工作的,特别是关于改变对象 (与原生数据类型相对)。我意识到you shouldn't use for_each this way,但这是为了学习。

我原以为这段代码会改变集合中的所有元素,但事实并非如此。

我的问题是: 1.为什么这段代码不会改变集合? 2.如何更改代码以便 修改集合?澄清:有没有办法保留for_each并让它操纵集合,或者这是不可能的,还必须使用其他一些方法(例如transform)?

代码

#include <iostream>
#include <algorithm>
#include <set>
using namespace std;

class A {
    int a;
public:
    A(int a) : a(a) {}
    int getA() const { return a; }
    void setA(int a) { this->a = a; }
    bool operator<(const A & b) const { return a<b.a; }
};

struct myprinter { 
    void operator()(const A & a) { cout << a.getA() << ", "; }  
};

struct doubler {
    void operator()(A a) { a.setA(a.getA()*2); }
};

int main() {
    int mynumbers[] = {8, 9, 7, 6, 4, 1};
    set<A> s1(mynumbers, mynumbers+6);
    for_each(s1.begin(), s1.end(), doubler()); //<-- Line in question
    for_each(s1.begin(), s1.end(), myprinter());
    return 0;
}

//Expected output: 2, 8, 12, 14, 16, 18
//Actual output: 1, 4, 6, 7, 8, 9,

到目前为止我尝试了什么

  • 起初我认为问题是倍增器是按值而不是通过引用获取参数,因此它没有将更改保存到集合中。但是当我将签名更改为void operator()(A & a)时,我收到错误:

    error: no match for call to '(doubler) (const A&)
    ' __f(*__first);
      ~~~^~~~~~~~~~
    error: binding 'const A' to reference of type 'A&' discards qualifiers
    

    我扣除了指出的那一行来自for_each的内部实现。我无法将参数设为const参考,因为我尝试使用a方法更改setA()值,因此它不能是const

  • 如果我将设置更改为矢量,那么当我更改doubler的签名以获取引用时,它会成功地将容器中的元素加倍。如果容器是一个集合,为什么它不起作用?

修改: moooeeeep与另一个问题that shows how to edit each element of a set相关联。这是我的问题的实用解决方案,但我的问题更具理论性 - 为什么你不能使用for_each编辑集合,你可以在哪里编辑向量和其他stl容器?

7 个答案:

答案 0 :(得分:4)

因为std::set管理它的元素的顺序,它禁止用户通过它的迭代器更改它的元素。这意味着它的begin()end()方法返回const_iterator。你只能读取迭代器指向的元素,而不是修改它(它是const),这是doubler()试图做的。

A solution只需使用std::vectorstd::sort自行订购:

#include <iostream>
#include <algorithm>
#include <vector>

class A {
    int a;
public:
    A(int a) : a(a) {}
    int getA() const { return a; }
    void setA(int a) { this->a = a; }
    bool operator<(const A & b) const { return a<b.a; }
};

struct myprinter { 
    void operator()(const A & a) { cout << a.getA() << ", "; }  
};

struct doubler {
    void operator()(A& a) { a.setA(a.getA()*2); } // by reference!
};

int main() {
    int mynumbers[] = {8, 9, 7, 6, 4, 1};
    std::vector<A> s1(mynumbers, mynumbers+6);
    std::sort(s1.begin(), s1.end());
    std::for_each(s1.begin(), s1.end(), doubler());
    std::for_each(s1.begin(), s1.end(), myprinter());
    return 0;
}

答案 1 :(得分:1)

问题是您不能修改std::set中的元素。如果有可能,那么它将如何处理这样的事情:

std::set<int> my_set { 1, 2, 3 };
int& foo = *(my_set.begin());
foo = 2;

现在有两个值为2的元素。由于

,在set中没有意义
  

std :: set是一个关联容器,包含一组排序为Key的唯一对象。

(强调我的)

http://en.cppreference.com/w/cpp/container/set

答案 2 :(得分:1)

根据评论,到目前为止,我已经了解了以下内容:

<强> 1。为什么这段代码不会改变集合?

因为传递给doubler方法的参数是通过值(C ++默认值)传递的,而不是通过引用传递的。因此,每个a参数的都会被修改,但这不会保存到容器中的元素中。

<强> 2。如何修改代码以便修改集合?

不容易做到。集合(与向量,deques,列表和数组等其他容器相对)维护其元素的顺序。 (相反,假设for_each方法中使用的函数否定了每个元素。然后该集合将向后排序,并且集合尝试维护的顺序将丢失)。因此,如果你想修改集合you have to do so one at a time的元素(谢谢你@NathanOliver和@moooeeeep的评论)

注意:如果容器不是一个集合(而不是vector或deque),则可以将doubler方法修改为以下内容以改变元素:

void operator()(A & a) { a.setA(a.getA()*2); }

答案 3 :(得分:0)

因为std::set中对象的内容决定了它的位置。这就是为什么std::set迭代器总是const

当然,通常情况下并非对象的所有成员都对其位置产生影响。然后可能的解决方法是声明那些成员mutable。然后,您可以使用const引用修改它们。

要修改影响对象在集合中的位置的成员,请从集合中删除对象,修改对象并将其重新添加。

答案 4 :(得分:0)

std::sethttp://www.cplusplus.com/reference/set/set/)的文档将std::set::iteratorbegin的非常量版本返回的end定义为:

  

const value_type

的双向迭代器

因此,即使是非const std::set::iterator也会返回const个对象。

答案 5 :(得分:0)

您不能简单地更改数据结构的键值存储类型中元素的键。 (如果是一组,它只是键,没有价值。)

如果愿意,可能需要调整底层容器中元素的位置,基于重新计算的哈希值(如果是无序哈希表)或二元搜索树中的不同排序位置/有序列表。

因此std::set的API要求您在这种情况下擦除并重新插入项目。

在这个方向上现有问题的答案已经说明了:

答案 6 :(得分:0)

for_each

的仿函数中修改元素实际上没有任何问题
  

如果InputIt是一个可变迭代器,f可以通过解除引用的迭代器修改范围的元素

接下来我们应该看看set的定义,请注意,由于澄清了这一点,因此它的iteratorconst_iterator类型都是:“Constant Bidirectional Iterators”

因此,使用set修改for_each是未定义的行为。