【问题标题】:Idiomatic way to cheaply remove an element from an arbitrary container?从任意容器中廉价删除元素的惯用方法?
【发布时间】:2018-01-10 18:23:49
【问题描述】:

所有 C++ 标准库容器都有一个insert() 方法;然而,它们并不是都有一个不带任何参数的remove() 方法,而是以任意顺序执行最便宜的删除。现在,对于不同的容器,这当然会有所不同:在向量中,我们将从后面删除,在单列表列表中,我们将从前面删除(除非我们保留指向尾部的指针),依此类推到实施细节。

那么,除了为每个容器滚动我自己的模板专业化之外,还有更惯用的方法吗?

【问题讨论】:

  • 并非如此。我的意思是,默认情况下你会从 unordered_set 中删除什么? “你”是指如果你必须为标准库编写这样的标准实用程序。
  • 在 C++17 中,您有用于关联容器的 extract 方法,但自然需要提供要删除的元素。
  • 您没有“一体化”容器。一些容器针对删除任意元素(如双端队列)进行了优化,一些针对添加元素进行了优化等。如果我们有一种廉价的方式来做 XXX,我们将只有一个容器。
  • 我能看到的唯一删除“随机”元素的用例是一个一个删除所有元素,而删除顺序无关紧要。但是通常更容易遍历所有元素(for (auto const& element: container),然后删除所有元素(container.clear())。这对于所有容器来说已经是统一的了。
  • 您不需要对每个容器模板进行专门化。只是此类模板的每个共同特征。例如,地图、集合、向量、列表都可以只弹出 back() 元素。显然,队列和堆栈将弹出它们指定的任何元素。从某种意义上说,您所说的是将 say std::queue 的行为与其存储实现分离(可能不是一个坏主意)。

标签: c++ containers c++-standard-library idioms


【解决方案1】:

标准容器的部分设计是,它们仅在可以为该容器提供最佳(通过选择的措施)的操作时才将操作作为成员函数提供。

如果一个容器提供了一个成员函数,那是因为有某种实现该函数的方法是该容器的最佳方法。

如果无法提供操作的最佳实现(如remove()),则不提供。

只有std::list 和(C++11 及更高版本)std::forward_list 设计用于高效删除元素,这就是为什么它们是唯一具有remove() 成员函数的容器。

其他容器不是为有效移除任意元素而设计的;

  • std::array 无法调整大小,因此拥有 insert()remove() 成员函数毫无意义。
  • std::deque 仅针对开头或结尾的删除进行了优化。
  • std::vector 中删除元素的效率低于其他容器,除了(可能)从末尾删除。

因此,为这些容器实现 remove() 成员函数违背了设计理念。

因此,如果您希望能够有效地从容器中移除元素,则需要为工作选择正确的容器。

为标准容器滚动您自己的包装器,以模拟某些容器不支持的操作,这完全是一种误导 - 从某种意义上说,它鼓励包装器类的用户相信他们不需要谨慎选择如果容器对性能或内存使用有特殊要求。

【讨论】:

    【解决方案2】:

    所以回答你的问题

    “那么,除了为每个容器滚动我自己的模板特化之外,还有更惯用的方法吗?

    有很多方法可以删除

    1. 序列容器和无序容器的erase() 返回下一个 被擦除项目后的迭代器。

    2. 关联容器的erase() 不返回任何内容。

    /* * 从向量或双端队列中删除 */

     vector<int> vec = {1, 4, 1, 1, 1, 12, 18, 16}; // To remove all '1'
      for (vector<int>::iterator itr = vec.begin(); itr != vec.end(); ++itr) {
         if ( *itr == 1 ) {
            vec.erase(itr);
         }
      }   // vec: { 4, 12, 18, 16}
      // Complexity: O(n*m)
    
      remove(vec.begin(), vec.end(), 1);  // O(n) 
                                          // vec: {4, 12, 18, 16, ?, ?, ?, ?}
    
    
    
    
      vector<int>::iterator newEnd = remove(vec.begin(), vec.end(), 1);   // O(n)
      vec.erase(newEnd, vec.end());  
    
      // Similarly for algorithm: remove_if() and unique()
    
    
      // vec still occupy 8 int space: vec.capacity() == 8
      vec.shrink_to_fit();   // C++ 11
      // Now vec.capacity() == 4 
    
      // For C++ 03:
      vector<int>(vec).swap(vec); // Release the vacant memory
    

    /* * 从列表中删除 */

      list<int> mylist = {1, 4, 1, 1, 1, 12, 18, 16};
    
      list<int>::iterator newEnd = remove(mylist.begin(), mylist.end(), 1);  
      mylist.erase(newEnd, mylist.end());
    
    
      mylist.remove(1);  // faster
    

    /* * 从关联容器或无序容器中删除 */

      multiset<int> myset = {1, 4, 1, 1, 1, 12, 18, 16};
    
      multiset<int>::iterator newEnd = remove(myset.begin(), myset.end(), 1);  
      myset.erase(newEnd, myset.end()); // O(n)
    
      myset.erase(1); // O(log(n)) or O(1)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-17
      • 1970-01-01
      • 2012-12-28
      • 1970-01-01
      • 2014-04-12
      • 1970-01-01
      • 2012-03-28
      • 1970-01-01
      相关资源
      最近更新 更多