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

Using an unordered_map where Key is a member of T

本文关键字:成员 Key 其中 unordered map 使用      更新时间:2023-10-16

是否有任何好的方法来使用unordered_map,以便您可以在常量时间(平均情况下)通过成员变量访问对象?下面的示例具有此功能,但需要将每个Person的名称复制为Key:

#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模板类,可以做到这一点整齐吗?

如果您不反对使用Boost,那么就使用Boost。MultiIndex使得这非常简单,而不会增加任何不必要的低效率。下面是一个有效地创建Person对象的unordered_set的示例,它以Person::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 -reference返回,而不是按值返回,但这个更改并不是严格要求的。)


应该注意的是,c++ 14对透明比较器的支持将允许您在这里使用std::unordered_set<Person>而不需要Boost。

显而易见的解决方案是复制关键部分,并使用std::unordered_map;对于小型的、局部的使用,这是最简单和最有效的方法这是首选的解决方案,但是没有强制的不变量key == mapped.key_part,这使得代码更难维护插入可能发生在代码中的多个不同位置。

如果您在映射中保留指针,而不是值,那么可以对映射进行包装,以便将键安排为指向适当的字段。当然,问题在于,这意味着保留指针,而不是值。

如果在映射中不修改值,则可以直接使用std::set,而不是std::map,使用适当的散列和平等的功能。如果你这样做,你可能需要构造一个虚拟Person对象,以便访问数据。

如果要修改值(当然不包括键),则您的问题是std::set只提供const访问其成员。你可以使用const_cast来解决这个问题,但它很难看。(而且容易出错;你如何确保真正的关键部件不是修改。)

这些解决方案都不完全令人满意。像你这样的案子没有对关键部分(名称)的可变访问,我可能会使用前两种解决方案之一,将unordered_map包裹在我自己的一个类,用来确保不变量被维护。

您正在寻找的是unordered_set,而不是地图。set使用该值作为键和值。

不要改变value的"key"部分