本文是小编为大家收集整理的关于使用一个unordered_map,其中Key是T的一个成员的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。
问题描述
有什么好方法可以使用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模板类吗?
推荐答案
如果您不反对使用 boost ,然后 boost.multiindex 使它变得非常简单,而无需添加任何不必要的效率.这是一个有效地创建Person对象的unordered_set的示例
#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的返回 - 参考而不是为了效率的依据,而不是严格要求更改.)
应注意,C ++ 14对透明的比较器任何需要提升的需要.
其他推荐答案
明显的解决方案是仅复制关键部分,然后使用 std::unordered_map;对于局部用途,这是最简单的 首选的解决方案,但没有任何实施不变的 key == mapped.key_part,这使得代码更难维护 插入可能发生在代码中的几个不同位置.
如果您将指针保存在地图中而不是值中,那么您可以 包裹地图以安排钥匙成为适当的指针 值中的字段.当然,问题是 保持指针,而不是价值.
如果您不在地图中修改它们,则只需使用 std::set,而不是std::map,带有适当的哈希 平等函数.如果这样做,您可能必须构建一个 虚拟Person为了访问数据.
如果要修改值(当然不包括密钥),则 您有一个问题,std::set仅提供const访问其 成员.您可以使用const_cast来解决这个问题,但这很丑陋. (容易出错;您如何确保实际关键部分不是 修改.)
这些解决方案都不令人满意.在像你这样的情况下 在没有可变访问关键部分(名称)的地方,我可能会 使用前两个解决方案之一,将unordered_map包装在 我自己的一类,以确保不变性.
其他推荐答案
您要寻找的是unordered_set,而不是地图.该集合使用该值作为密钥和值.
只是不要更改值的"键"部分.
问题描述
Is there any nice way to use an unordered_map so that you can access objects by a member variable in constant time (average case)? The following example has this functionality but requires the name of each Person to be duplicated as the 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; }
I'm thinking that somehow the value_type would need to be Person itself, instead of a pair<string, Person> and the unordered_map would need to know to use Person::getName when hashing and accessing objects.
The ideal solution would allow me to set up an unordered_map (or unordered_set if it's more apt for the job) that knows to use Person::getName to get the key of each object. I would then be able to insert them simply by giving the object (and no key because it knows how to get the key) and access them by giving keys that would compare equal to the return value of Person::getName.
Something along the lines of:
// Pseudocode std::unordered_map<Person, Person::getName> map = {p1, p2}; map["dave"].kill();
So is it possible to instantiate an unordered_map template class that can do this neatly?
推荐答案
If you're not opposed to using Boost, then Boost.MultiIndex makes this extremely simple without adding any needless inefficiency. Here's an example that effectively creates an unordered_set of Person objects that is keyed on the value of 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; }
(Note that I changed the signature of Person::getName() to return by const-reference rather than by value for efficiency's sake, but the change isn't strictly required.)
It should be noted that C++14's support for transparent comparators would allow you to use std::unordered_set<Person> here without any need for Boost.
其他推荐答案
The obvious solution is to just duplicate the key part, and use std::unordered_map; for small, localized use, this is the simplest and the preferred solution, but there is nothing enforcing the invariant key == mapped.key_part, which make the code harder to maintain when insertions may occur at several different places in the code.
If you're keeping pointers in the map, rather than values, then you can wrap the map to arrange for the key to be a pointer to the appropriate field in the value. The problem with this is, of course, that it means keeping pointers, and not values.
If you don't modify the values once they're in the map, you can just use std::set, rather than std::map, with an appropriate hash and equality function. If you do this, you'll probably have to construct a dummy Person object in order to access the data.
If you want to modify the values (excluding the key, of course), then you have the problem that std::set only provides const access to its members. You can get around this using const_cast, but it's ugly. (And error prone; how do you ensure that the actual key part isn't modified.)
None of this solutions is entirely satisfactory. In a case like yours, where there's no mutable access to the key part (the name), I'd probably go with one of the first two solutions, wrapping the unordered_map in a class of my own to ensure that the invariants were maintained.
其他推荐答案
What you're looking for is an unordered_set, not a map. The set uses the value as both key and value.
Just don't change the "key" part of the value.