【问题标题】:How can I remove an item from a set in C++ without deleting it?如何从 C++ 中的集合中删除一个项目而不删除它?
【发布时间】:2019-04-10 22:14:10
【问题描述】:

在 C++ 中使用 std::set,我能找到从集合中删除项目的唯一方法是使用擦除方法。这会删除有问题的项目,我不希望发生这种情况。我能想到的从集合中删除一个项目而不删除它的唯一方法是创建一个新集合并迭代地将旧集合的所有项目添加到其中,确保不添加需要从中删除的项目集,然后删除旧集。

有没有更简洁的方法来做到这一点?

【问题讨论】:

  • 困惑因为:方法(1)和方法(2)都删除了有问题的项目。
  • 假设您确实创建了一个新集。您将如何将任何现有项目移入其中?如果您的意思是从字面上使用移动语义,那将意味着删除旧项目,这是您说您不想做的事情。如果不移动语义,它会在什么意义上移动?
  • 准确理解为什么不希望删除原始对象会很有帮助。如果是因为,例如,你在某处隐藏了对它的引用,而不是因为它的构建成本很高,那么它会产生巨大的差异。
  • 不清楚你的意思。您可以将项目复制到另一个位置(如矢量),然后将其从集合中删除。您仍在删除原始值,但您在另一个容器中拥有该值的副本。这是你的意思吗?

标签: c++ stl set


【解决方案1】:

您无法从集合中移除一个项目而不删除它。集合拥有他们的成员。如果该成员从集合中删除,则它不再存在。如果您希望能够在不删除的情况下删除某些内容,请不要将其添加到集合中。

想象一下,如果你有int x[5]; x[2]=2;。你怎么能把x[2]从数组中取出来?这甚至意味着什么?当然,您可以构造一个具有相同值的新整数int j = x[2];。但这是一个新对象(具有相同的值),不会延长现有对象的寿命。

根据您的外部问题,可能会有解决方案。例如,您可以将std::unique_ptr 添加到一个对象到一个集合中,然后您可以销毁该std::unique_ptr 而不会破坏它指向的对象,该对象正在为同一底层对象构造一个新的std::unique_ptr

【讨论】:

  • 除了你可以 - 你可以使用 extract + std::move 来转移元素的所有权
  • @JorgePerez 这不会转移元素的所有权。这会删除现有元素,同时创建一个与现有元素具有相同值的新元素。 (除非您想将旧元素放入相同类型的容器中。)
  • 如果现有元素不拥有任何资源,那么复制它并不重要。如果它确实拥有资源,那么这将转移资源的所有权。
  • @JorgePerez 复制不拥有任何资源的元素并销毁原始元素会使所有指向它的指针和引用无效。所以你不能真的说“没关系”。
  • 这个答案概述了唯一明智的选择。
【解决方案2】:

将对象移出集合

您可以使用extract 从集合中删除相应的节点。这为您提供了节点的句柄。拿到手柄后,您可以将物品移出手柄。

template<class T>
T find_and_remove(std::set<T>& s, T const& elem) {
    auto iterator = s.find(elem); 
    if(iterator == s.end() {
        throw std::invalid_argument("elem not in set"); 
    }
    // Remove element, return node handle
    auto node_handle = s.extract(iterator);
    return std::move(node_handle.value());
}

或者,如果你已经有了节点的迭代器,你可以这样写:

template<class T>
T remove_from_set(std::set<T>& s, std::set<T>::iterator it) {
    return std::move(s.extract(it).value());
}

移动价值会转移价值拥有的任何资源的所有权。例如,如果集合包含一个字符串,则不会删除该字符串的内容,并且该字符串的任何迭代器都不会失效。

需要注意的是,如果您在对象仍在集合中时就拥有指向该对象的指针或引用,则这些将无效。

在不移动且不使对象的任何指针或引用无效的情况下提取对象本身

这是不太常见的情况,但如果您有指向集合中对象的引用或指针,您可能想要这样做。

同样,我们可以使用extract函数:

auto node_handle = s.extract(my_object);

或者:

auto node_handle = s.extract(my_iterator); 

您可以使用node_handle.value() 访问存储的对象,这将返回对该对象的引用。在删除 node_handle 之前,该对象不会被删除,如果您需要进一步延长它的生命周期,您可以从函数中返回 node_handle 而不会删除该对象。

【讨论】:

  • 您的 remove_from_set 返回一个与原始值具有相同值的新对象并销毁原始对象——这正是 OP 所说的他们不想做的事情。
  • 如果原始值支持移动语义,则原始值被破坏并不重要。任何资源都将被转移而不被删除。
  • 正如我在回复您对我的回答的评论时所说的那样,这是错误的。这可能非常重要,例如,指针或对它的引用是否已隐藏在某处。我们不知道为什么 OP 不希望删除原始对象,但他们说这是他们想要避免的。
  • 您的最后一次编辑使您的答案非常错误。例如,如果字符串使用短字符串优化并且迭代器包含指向 std::string 对象本身内的缓冲区的指针。
  • 我更新了我的答案,澄清和区分“从集合中移动”和“在不使任何现有指针或引用无效的情况下删除”。您对这些变化满意吗?
猜你喜欢
  • 2021-02-15
  • 1970-01-01
  • 2013-01-17
  • 2011-01-17
  • 1970-01-01
  • 2017-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多