【问题标题】:Using STL containers with a class containing its own key将 STL 容器与包含自己的键的类一起使用
【发布时间】:2014-08-25 15:11:49
【问题描述】:

我有一个对象,可以通过它的名字来识别,我想放置它在 STL 容器之一中。

class MyClass {
public:
    //getters and setters, other functions
private:
    std::string name;
    //other member variables
};

所以起初我认为 类似地图的结构 的使用与我的情况无关,因为在这些结构中,标识符(键)与类本身。使用映射,我必须返回 name 变量并将其复制到“外部”类(浪费内存和不合逻辑,违反 OOP 规则)。

我的下一个镜头是使用类似集合的结构。在这种情况下,我只有关键字段,我在其中加载整个对象。使用这种方法,我必须重载 和 == 运算符才能使用对象作为键。如果我使用 unordered_set,我什至可以制作一个用于散列的函子,它工作得很好。这里的问题是 我不能像使用地图那样使用容器函数。这是有效的mapInstance.find("example"),而不是setInstance.find("example")。我必须创建一个将成员变量name 设置为“example”的对象并将其传递给find() 函数。这个解决方案的问题是我的类中的其他成员变量是重复且未使用。我什至尝试为std::stringMyClass 类重载 和== 运算符,如果我像stringInstance < MyClassInstance 这样使用它们,它工作得很好,但是容器函数无法使用(我什至尝试将函子重载到使用没有成功的字符串)。

你能建议我一个简单的方法(或方法),如何用std::setstd::map(也许是其他人)解决这个问题? 在std::map 中,密钥不能是引用(据我所知),我不知道如何用std::set 解决它。

注意: 将指针存储在map 的关键字段中的问题在于,如果我们改变主意并使用unordered_map 而不是@987654336 @,hash 会根据指针计算,而不是根据字符串计算(hash 函数可以被覆盖,但是对于一个简单的任务来说似乎很复杂)。

感谢您的帮助!

【问题讨论】:

  • 你考虑过find_if而不是find吗?
  • 至少在C++14中,你应该可以使用find without constructing a key
  • @Angew,我相信要使其正常工作,您需要确保提供std::less<>is_transparent 的其他内容,而不是使用默认的std::less
  • 字符串标识符是否必须是动态的和/或字符串?
  • @Angew 我想你刚刚解决了我的问题!你能告诉我一个例子如何在 std::set 上使用它吗?

标签: c++ map stl unordered


【解决方案1】:

您应该问问自己您对容器的要求是什么。

需要考虑的事项是:

  • 容器中通常有多少对象
  • 什么是内存限制
  • 搜索对象的频率以及可接受的复杂性是多少?

一个 std::map 有一些可能与你的类冲突的要求。例如。将元素添加到地图后,不允许更改键。但是,您的班级可能每次都更改名称。从这个考虑可以清楚地看出,std::map 不能使用对字符串的引用作为键。

在最简单的情况下,您可以考虑使用带有谓词的std::liststd::find_if 来检查特殊名称。这将具有 O(n) 复杂度。

【讨论】:

  • 是的,我知道名字应该是一个常数。在我的项目中,它像受保护的成员一样实现,没有设置函数。类的构造函数设置 name 变量。它受到保护,因为子类可能会更改字符串。
【解决方案2】:

有时,您必须在提供的基础上再接再厉,才能获得理想的结果。我建议您寻找适合您用例的专用适配器。

这是一个非常基本的实现,您可以在此基础上进行构建。在许多情况下,使用std::vector 获得的缓存位置会比使用提供类似功能集的标准容器提供更好的性能。

给定一个简单的类型和一些辅助操作符:

struct Obj
{
   int key;
   std::string name;
};

bool operator<(const Obj& lhs, const Obj& rhs)
{
   return lhs.key < rhs.key;
}

bool operator==(const Obj& lhs, int rhs)
{
   return lhs.key == rhs;
}

bool operator<(const Obj& lhs, int rhs)
{
   return lhs.key < rhs;
}

我们可以设计一个简单的flat_map 类,它提供地图的基本复杂性保证,但将满足您在不构造值类型的情况下通过键查找对象的要求(就像您需要使用一套)。插入提供了更多的复杂性,但查找具有相似的复杂性。如果查找发生得更频繁,那么插入(大多数时候都是这种情况)可以很好地工作。

class flat_map
{
public:
   using container_type = std::vector<Obj>;

   // insert object into the set
   // complexity varies based on length of container
   void insert(Obj&& obj)
   {
      container_.emplace_back(std::move(obj));
      std::sort(container_.begin(), container_.end());
   }

   // find with O(log N) complexity    
   container_type::iterator find(int key)
   {
      auto it = std::lower_bound(container_.begin(), container_.end(), key);

      if(it != container_.end() && *it == key)
         return it;

      return container_.end();
   }

private:
   container_type container_;
};

示例用法:

int main()
{
   flat_map obj;

   obj.insert({1, "one"});
   obj.insert({2, "two"});
   obj.insert({3, "three"});

   auto it = obj.find(2);

   std::cout << it->key << ' ' << it->name << '\n';
}

您可以以任何您认为合适的方式扩展flat_map 适配器。添加所需的重载以满足您的要求,模板化参数(分配器、比较、存储类型等)。

【讨论】:

  • 就我而言,我绝对应该使用 std::map、std::set、std::unordered_map 或 std::unordered_set,因为我需要快速查找时间。做一个包装器不是一个坏主意,但我必须以某种方式使用容器的内部查找函数,因为它是专门为底层数据结构编写的。
  • 我可以使用地图,但是有这个“重复键”问题。如果我能解决这个问题,我就完成了。
  • 如果我使用一个集合,我必须以某种方式使搜索成为可能,而不是创建一个名为“example”的新对象,然后将它传递给 find 函数。
  • @CeruleanKnight 复制密钥真的有问题吗?你内存受限吗?或者只是你不喜欢这个想法,因为它似乎不必要的多余?您可以管理一组键字符串/列表并使用指向它的(智能)指针或引用包装器,但如果不是真的需要,我个人不想付出增加复杂性的代价。而且它仍然认为,如果您之后更改密钥,地图将不会很好玩。
  • 这个flat_map 提供与std::setstd::map 相同的查找复杂性,并且由于std::vector 提供更好的缓存局部性,实际上可能更快,具体取决于您的具体用例。它显然不如“无序”版本,但您也可以采用相同的方法并将其应用于哈希表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-07
  • 2011-03-22
  • 1970-01-01
  • 1970-01-01
  • 2011-01-08
  • 1970-01-01
相关资源
最近更新 更多