【问题标题】:Is there a C++ container for unique values that supports strict size checking?是否有支持严格大小检查的唯一值的 C++ 容器?
【发布时间】:2017-12-13 11:23:06
【问题描述】:

我正在寻找一个 C++ 容器来存储指向同时满足以下要求的对象的指针。

  • 保持元素顺序的容器(序列容器,所以std::set不适合)
  • 具有返回实际大小的成员函数的容器(std::array::size() 总是返回固定大小,std::array 不适合)
  • 支持随机访问的容器,例如operator []

这是我的代码 sn-p,我想删除用于检查元素大小和唯一性的断言。

#include <vector>
#include <set>
#include "assert.h"

class Foo {
  public:
    void DoSomething() {
    }
};

int main() {
  // a variable used to check whether a container is properly assigned
  const uint8_t size_ = 2; 

  Foo foo1;
  Foo foo2;

  // Needs a kind of sequential containers to keep the order
  // used std::vector instead of std::array to use member function size()
  const std::vector<Foo*> vec = {
    &foo1,
    &foo2
  };

  std::set<Foo*> set_(vec.begin(), vec.end()); 
  assert(vec.size() == size_); // size checking against pre-defined value
  assert(vec.size() == set_.size()); // check for elements uniqueness

  // Needs to access elements using [] operator
  for (auto i = 0; i < size_; i++) {
    vec[i]->DoSomething();
  }

  return 0;
}

是否有一个 C++ 容器不需要在我的代码 sn-p 中使用两个断言?或者我是否需要创建自己的类来封装其中一个 STL 容器?

【问题讨论】:

  • 为什么不使用 std::vector?
  • 我还建议您花一些时间阅读about the XY problem,并思考这个问题可能是一个问题,以及如何改进它。
  • 啊,我猜是条目的唯一性是问题?不幸的是,那只是在标题中,所以很容易错过。使用散列来管理唯一性,同时使用向量来管理索引。将其全部包装在一个对象中,该对象公开 set insert 方法,但其他所有内容为向量。可能从向量派生得到曝光。
  • @Someprogrammerdude 据我所知,std::unordered_set 以任意顺序存储元素,我想要一个可以指定顺序的元素。
  • 看起来你想要的是包含一个向量的东西,带有push_back[] operator 和你需要的任何其他东西——它自己执行唯一检查;或者包装一个集合,然后自己执行随机访问操作

标签: c++ c++11 stl containers


【解决方案1】:

所以一个类的行为就像一个向量,除非你插入,它拒绝像集合或地图这样的重复。

一个选项可能是具有 T* 和 sequence_index 索引的 Boost.Bimap。 您的类似矢量的索引将通过 sequence_index。删除元素后,您甚至可能愿意忍受序列中的漏洞。

坚持使用 STL,您可以使用 2 个地图实现双向地图,或者以下使用地图和矢量:

请注意,通过从向量继承,我可以免费获得所有向量方法,但我也冒着用户向下转换为向量的风险。
在不使用包装器(la queue vs list)进行重构的情况下,一种解决方法是使其受保护继承,然后显式使用所有方法返回公共。这实际上更安全,因为它可以确保您不会无意中留下一些会使两个容器失步的矢量修改方法。

还请注意,如果您希望过滤掉任何重复项,则需要滚动自己的 initializer_list 构造函数。而且你必须做一些工作才能让这个线程安全。

template <class T>
class uniqvec : public std::vector<T*>
{
private:
    typedef typename std::vector<T*> Base;
    enum {push_back, pop_back, emplace_back, emplace}; //add anything else you don't like from vector
std::map <T*, size_t> uniquifier;
public:

    std::pair<typename Base::iterator, bool> insert(T* t)
    {
        auto rv1 = uniquifier.insert(std::make_pair(t, Base::size()));
        if (rv1.second)
        {
            Base::push_back(t);
        }
        return std::make_pair(Base::begin()+rv1.first.second, rv1.second);
    }
    void erase(T* t)
    {
        auto found = uniquifier.find(t);
        if (found != uniquifier.end())
        {
            auto index = found->second;
            uniquifier.erase(found);
            Base::erase(Base::begin()+index);
            for (auto& u : uniquifier)
                if (u.second > index)
                    u.second--;
        }
    }
    // Note that c++11 returns the next safe iterator,
    // but I don't know if that should be in vector order or set order.
    void erase(typename Base::iterator i)
    {
        return erase(*i);
    }
};

【讨论】:

    【解决方案2】:

    正如其他人所提到的,您的特定问题看起来像是 XY 问题(您对特定解决方案感到困惑,而不是专注于原始问题)。几年前这里提供了一个非常有用的流程图(感谢@MikaelPersson),它将帮助您选择最适合您需求的特定 STL 容器。你可以在这里找到原始问题In which scenario do I use a particular STL container?

    【讨论】:

      猜你喜欢
      • 2012-11-06
      • 2015-08-24
      • 2021-07-06
      • 1970-01-01
      • 2018-04-19
      • 2013-10-15
      • 2013-12-11
      • 1970-01-01
      • 2016-07-11
      相关资源
      最近更新 更多