C ++ std :: set update很乏味:我无法更改元素

时间:2010-02-07 18:59:25

标签: c++ stl set

我发现std::set上的更新操作很乏味,因为cppreference上没有这样的API。所以我现在做的是这样的事情:

//find element in set by iterator
Element copy = *iterator;
... // update member value on copy, varies
Set.erase(iterator);
Set.insert(copy);

基本上Set返回的迭代器是const_iterator,你不能直接改变它的值。

有更好的方法吗?或者我可以通过创建自己的std::set来覆盖{{1}}(我不确切知道它是如何工作的......)

7 个答案:

答案 0 :(得分:70)

set返回const_iterators(标准显示set<T>::iteratorconst,而set<T>::const_iteratorset<T>::iterator实际上可能属于同一类型 - 参见n3000.pdf中的23.2.4 / 6)因为它是一个有序的容器。如果它返回常规iterator,您将被允许从容器下面更改项值,可能会改变排序。

您的解决方案是改变set中项目的惯用方法。

答案 1 :(得分:22)

在简单的情况下,有两种方法可以做到这一点:

  • 您可以对不属于该键的变量使用mutable
  • 您可以在Key Value对中拆分课程(并使用std::map

现在,问题是针对棘手的情况:当更新实际修改对象的key部分时会发生什么?你的方法有效,但我承认这很乏味。

答案 2 :(得分:8)

更新:虽然现在的情况如下,但该行为被视为defect,并将在即将发布的标准版本中进行更改。多么难过。


有几点让你的问题相当混乱。

  1. 函数可以返回值,类不能。 std::set是一个类,因此无法返回任何内容。
  2. 如果您可以致电s.erase(iter),则iter不是const_iteratorerase需要非const迭代器。
  3. 返回迭代器的std::set的所有成员函数返回非const迭代器,只要该集合也是非const。
  4. 只要更新不改变元素的顺序,就可以更改集合元素的值。以下代码编译并正常工作。

    #include <set>
    
    int main()
    {
        std::set<int> s;
        s.insert(10);
        s.insert(20);
    
        std::set<int>::iterator iter = s.find(20);
    
        // OK
        *iter = 30;
    
        // error, the following changes the order of elements
        // *iter = 0;
    }
    

    如果您的更新更改了元素的顺序,则必须擦除并重新插入。

答案 3 :(得分:7)

您可能希望使用std::map代替。使用影响键排序的Element部分,并将所有Element作为值。将会有一些小的数据重复,但你会有更容易(也可能更快)的更新。

答案 4 :(得分:6)

在C ++ 17中,借助extract()P0083可以做得更好:

// remove element from the set, but without needing
// to copy it or deallocate it
auto node = Set.extract(iterator);
// make changes to the value in place
node.value() = 42;
// reinsert it into the set, but again without needing 
// to copy or allocate
Set.insert(std::move(node));

这将避免您的类型的额外副本和额外的分配/取消分配,并且还将与仅移动类型一起使用。

您也可以通过键extract。如果没有密钥,将返回一个空节点:

auto node = Set.extract(key);
if (node) // alternatively, !node.empty()
{
    node.value() = 42;
    Set.insert(std::move(node));
}

答案 5 :(得分:2)

我在C ++ 11中遇到了同样的问题,其中::std::set<T>::iterator确实是常量,因此不允许更改其内容,即使我们知道转换不会影响<不变量。您可以通过将::std::set包装到mutable_set类型或为内容编写包装来解决此问题:

  template <typename T>
  struct MutableWrapper {
    mutable T data;
    MutableWrapper(T const& data) : data(data) {}
    MutableWrapper(T&& data) : data(data) {}
    MutableWrapper const& operator=(T const& data) { this->data = data; }
    operator T&() const { return data; }
    T* operator->() const { return &data; }
    friend bool operator<(MutableWrapper const& a, MutableWrapper const& b) {
      return a.data < b.data;
    }   
    friend bool operator==(MutableWrapper const& a, MutableWrapper const& b) {
      return a.data == b.data;
    }   
    friend bool operator!=(MutableWrapper const& a, MutableWrapper const& b) {
      return a.data != b.data;
    }   
  };

我发现这个更简单,它可以在90%的情况下工作,而用户甚至没有注意到集合和实际类型之间有什么东西。

答案 6 :(得分:0)

在某些情况下这会更快:

std::pair<std::set<int>::iterator, bool> result = Set.insert(value);
if (!result.second) {
  Set.erase(result.first);
  Set.insert(value);
}

如果该值通常不在std::set中,那么这可以有更好的效果。