C++ std::set 更新很乏味:我无法就地更改元素

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2217878/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 22:35:30  来源:igfitidea点击:

C++ std::set update is tedious: I can't change an element in place

c++stlset

提问by Figo

I find the update operation on std::settedious since there's no such an API on cppreference. So what I currently do is something like this:

我发现更新操作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);

Basically the iterator return by Setis a const_iteratorand you can't change its value directly.

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

Is there a better way to do this? Or maybe I should override std::setby creating my own (which I don't know exactly how it works..)

有一个更好的方法吗?或者也许我应该std::set通过创建自己的来覆盖(我不知道它是如何工作的......)

回答by Terry Mahaffey

setreturns const_iterators(the standard says set<T>::iteratoris const, and that set<T>::const_iteratorand set<T>::iteratormay in fact be the same type - see 23.2.4/6 in n3000.pdf) because it is an ordered container. If it returned a regular iterator, you'd be allowed to change the items value out from under the container, potentially altering the ordering.

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

Your solution is the idiomatic way to alter items in a set.

您的解决方案是更改set.

回答by Matthieu M.

There are 2 ways to do this, in the easy case:

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

  • You can use mutableon the variable that are not part of the key
  • You can split your class in a KeyValuepair (and use a std::map)
  • 您可以mutable在不属于密钥的变量上使用
  • 您可以将班级分成一KeyValue对(并使用std::map

Now, the question is for the tricky case: what happens when the update actually modifies the keypart of the object ? Your approach works, though I admit it's tedious.

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

回答by Barry

In C++17 you can do better with extract(), thanks to P0083:

在 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));

This will avoid an extra copy of your type and an extra allocation/deallocation, and will also work with move-only types.

这将避免您的类型的额外副本和额外的分配/解除分配,并且也适用于仅移动类型。

You can also extractby key. If the key is absent, this will return an empty node:

也可以extract按键。如果键不存在,这将返回一个空节点:

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

回答by avakar

Update:Although the following is true as of now, the behavior is considered a defectand will be changed in the upcoming version of the standard. How very sad.

更新:尽管目前以下情况属实,但该行为被视为缺陷,并将在即将发布的标准版本中进行更改。多么悲伤。



There are several points that make your question rather confusing.

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

  1. Functions can return values, classes can't. std::setis a class, and therefore cannot return anything.
  2. If you can call s.erase(iter), then iteris not a const_iterator. eraserequires a non-const iterator.
  3. All member functions of std::setthat return an iterator return a non-const iterator as long as the set is non-const as well.
  1. 函数可以返回值,类不能。std::set是一个类,因此不能返回任何东西。
  2. 如果可以调用s.erase(iter),那就iter不是了const_iteratorerase需要一个非常量迭代器。
  3. std::set只要集合也是非常量的,它的所有成员函数都返回一个迭代器,返回一个非常量迭代器。

You are allowed to change the value of an element of a set as long as the update doesn't change the order of elements. The following code compiles and works just fine.

只要更新不改变元素的顺序,您就可以更改集合中元素的值。下面的代码编译并运行得很好。

#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;
}

If your update changes the order of elements, then you have to erase and reinsert.

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

回答by avakar

You may want to use an std::mapinstead. Use the portion of Elementthat affects the ordering the key, and put all of Elementas the value. There will be some minor data duplication, but you will have easier (and possibly faster) updates.

您可能想std::map改用 。使用Element影响键排序的部分,并将所有Element作为值。会有一些轻微的数据重复,但您将获得更容易(并且可能更快)的更新。

回答by bitmask

I encountered the very same issue in C++11, where indeed ::std::set<T>::iteratoris constant and thus does not allow to change its contents, even if we know the transformation will not affect the <invariant. You can get around this by wrapping ::std::setinto a mutable_settype or write a wrapper for the content:

我在 C++11 中遇到了同样的问题,其中确实::std::set<T>::iterator是常量,因此不允许更改其内容,即使我们知道转换不会影响<不变量。您可以通过包装::std::setmutable_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;
    }   
  };

I find this much simpler and it works in 90% the cases without the user even noticing there to be something between the set and the actual type.

我发现这要简单得多,它在 90% 的情况下都有效,用户甚至没有注意到集合和实际类型之间存在某些东西。

回答by jcoffland

This is faster in some cases:

在某些情况下这更快:

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

If the value is usually not already in the std::setthen this can have better performance.

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