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

发布于 2025-01-15 17:43:36 字数 493 浏览 0 评论 0原文

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

//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

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

//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 Set is a const_iterator and you can't change its value directly.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

你与清晨阳光 2025-01-22 17:43:37

我在 C++11 中遇到了同样的问题,其中 ::std::set::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% 的情况下工作在这种情况下,用户甚至没有注意到集合和实际类型之间存在某些东西。

I encountered the very same issue in C++11, where indeed ::std::set<T>::iterator is 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::set into a mutable_set type or write a wrapper for the content:

  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.

随遇而安 2025-01-22 17:43:37

在某些情况下,这会更快:

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

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

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::set then this can have better performance.

混吃等死 2025-01-22 17:43:36

set 返回 const_iterators (标准规定 set::iteratorconst,并且 set::const_iteratorset::iterator 实际上可能是相同的类型 - 请参阅 23.2.4/6 n3000.pdf),因为它是一个有序的容器。如果它返回常规迭代器,您将被允许从容器下更改项目值,这可能会改变顺序。

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

set returns const_iterators (the standard says set<T>::iterator is const, and that set<T>::const_iterator and set<T>::iterator may 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.

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

煮茶煮酒煮时光 2025-01-22 17:43:36

C++17 引入了 extract,请参阅 Barry 的回答


如果您坚持使用旧版本,有两种方法可以做到这一点,在简单的情况下:

  • 您可以在不属于键的变量上使用 mutable
  • 您可以将您的类拆分为一个 Key Value 对(并使用 std::map

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

C++17 introduced extract, see Barry's answer.


If you're stuck with an older version, there are 2 ways to do this, in the easy case:

  • You can use mutable on the variable that are not part of the key
  • You can split your class in a Key Value pair (and use a std::map)

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

活雷疯 2025-01-22 17:43:36

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

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

您还可以通过按键提取。如果键不存在,这将返回一个空节点:

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

In C++17 you can do better with extract(), thanks to 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 extract by key. If the key is absent, this will return an empty node:

auto node = Set.extract(key);
if (node) // alternatively, !node.empty()
{
    node.value() = 42;
    Set.insert(std::move(node));
}
短暂陪伴 2025-01-22 17:43:36

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


有几点使您的问题相当令人困惑。

  1. 函数可以返回值,而类则不能。 std::set 是一个类,因此不能返回任何内容。
  2. 如果您可以调用 s.erase(iter),那么 iter 就不是 const_iteratorerase 需要一个非常量迭代器。
  3. 只要集合也是非常量,所有返回迭代器的 std::set 成员函数都会返回非常量迭代器。

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

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

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

Update: Although the following is true as of now, the behavior is considered a defect and 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::set is a class, and therefore cannot return anything.
  2. If you can call s.erase(iter), then iter is not a const_iterator. erase requires a non-const iterator.
  3. All member functions of std::set that return an iterator return a non-const iterator as long as the set is non-const as well.

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.

离鸿 2025-01-22 17:43:36

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文