使用unordered_map,其中Key是T的成员

时间:2011-08-16 08:40:36

标签: c++ stl c++11 unordered-map

有没有什么好方法可以使用unordered_map,以便您可以在常量时间(平均大小写)中通过成员变量访问对象?以下示例具有此功能,但要求将每个Person的名称复制为密钥:

#include <iostream>
#include <string>
#include <unordered_map>
#include <algorithm>

class Person {
 public:
  Person() : name_("") {}
  Person(const std::string& name) : name_(name) {}
  std::string getName() const { return name_; }
  void kill() const { std::cout << name_ << " is dead!" << std::endl; }
 private:
  std::string name_;
};

int main(int argc, const char* argv[]) {
  Person p1("dave");
  Person p2("bob");

  std::unordered_map<std::string, Person> map = {
    {p1.getName(), p1}, // Duplicating the
    {p2.getName(), p2}  // keys here
  };

  map["dave"].kill();
  return 0;
}

我认为value_type本身需要Person本身,而不是pair<string, Person>unordered_map需要知道使用Person::getName当散列和访问对象时。

理想的解决方案允许我设置unordered_map(或unordered_set,如果它更适合作业)知道使用Person::getName来获取每个对象的密钥。然后,我可以简单地通过给出对​​象来插入它们(并且没有键,因为它知道如何获取密钥)并通过给出比较等于Person::getName的返回值的键来访问它们。

有些事情:

// Pseudocode
std::unordered_map<Person, Person::getName> map = {p1, p2};
map["dave"].kill();

那么可以实例化一个可以做到这一点的unordered_map模板类吗?

3 个答案:

答案 0 :(得分:3)

如果您不反对使用Boost,那么Boost.MultiIndex会使这非常简单,而不会增加任何不必要的低效率。以下是一个有效创建unordered_set PersonPerson::getName()个对象的示例,该对象以#include <string> #include <iostream> #include <boost/multi_index_container.hpp> #include <boost/multi_index/indexed_by.hpp> #include <boost/multi_index/hashed_index.hpp> #include <boost/multi_index/mem_fun.hpp> namespace bmi = boost::multi_index; struct Person { Person() = default; Person(std::string const& name) : name_(name) { } std::string const& getName() const noexcept { return name_; } void kill() const { std::cout << name_ << " is dead!\n"; } private: std::string name_; }; typedef bmi::multi_index_container< Person, bmi::indexed_by< bmi::hashed_unique< bmi::const_mem_fun<Person, std::string const&, &Person::getName> > > > PersonUnorderedSet; int main() { Person p1("dave"); Person p2("bob"); PersonUnorderedSet set; set.insert(p1); set.insert(p2); set.find("dave")->kill(); // for exposition, find is actually returning an iterator PersonUnorderedSet::const_iterator iter = set.find("bob"); if (iter != set.end()) iter->kill(); // set semantics -- newly_added is false here, because // the container already contains a Person named 'dave' bool const newly_added = set.insert(Person("dave")).second; } 的值为基础:

Person::getName()

(请注意,为了提高效率,我将const的签名更改为std::unordered_set<Person> - 引用而不是值,但不是严格要求更改。)


应该注意的是,C ++ 14对transparent comparators的支持将允许您在此处使用{{1}}而无需任何Boost。

答案 1 :(得分:2)

显而易见的解决方案是只复制关键部分,然后使用 std::unordered_map;对于小型的本地化使用,这是最简单的 首选解决方案,但没有强制执行不变量 key == mapped.key_part,这使代码难以维护 插入可能出现在代码中的几个不同位置。

如果你在地图中保留指针而不是值,那么你可以 包装地图以安排键成为指向适当的指针 值中的字段。当然,问题在于它意味着什么 保持指针,而不是价值观。

如果您在地图中修改了值,则可以使用 std::set,而不是std::map,具有适当的哈希值 平等功能。如果你这样做,你可能需要构建一个 虚拟Person对象,以便访问数据。

如果要修改值(当然不包括键),那么 您遇到std::set仅提供const访问权限的问题 成员。你可以使用const_cast解决这个问题,但这很难看。 (并且容易出错;如何确保实际的关键部分不是 修改。)

这些解决方案都不是完全令人满意的。在像你这样的情况下, 我可能没有对关键部分(名称)的可变访问权限 使用前两个解决方案之一,将unordered_map包装进去 我自己的一类,以确保维持不变量。

答案 2 :(得分:1)

您要找的是unordered_set,而不是地图。该集合将值用作键和值。

只是不要更改值的“关键”部分。