问题
I'm trying to use both a list and an unordered_map to store the same set of objects. I'm new to C++, so still getting comfortable with iterators.
Say I have the following test code:
class Test {
public:
int x;
int y;
int z;
Test (int, int, int);
}
Test t1 = Test(1,2,3);
Test t2 = Test(2,4,6);
Test t3 = Test(3,6,9);
std::list<Test> list;
std::unordered_map<int, Test> map;
list.push_back(t3);
list.push_back(t2);
list.push_back(t1);
map[101] = t1;
map[102] = t2;
map[103] = t3;
Is it possible to look up an object by key, and then generate a list iterator from the reference of the object (or from the unordered_map generator?)
So if I have the key 102, I could look up t2 in constant time. I then want to iterate forward/backward/insert/delete relative to t2's position in the list.
I can use find to get a unordered_map iterator pointing to t2. I don't know how to generate a list iterator that starts at t2 (I can only generate iterators at the beginning or the end of the list, and iterate through.)
Would appreciate anyone pointing me to good tutorials on the STL and iterators.
Thanks!
Afterthought: Is this an acceptable approach? I have many objects and need to efficiently look them up by integer key. I also need to preserve their order (unrelated to these integer keys) and insert/delete/traverse efficiently.
回答1:
While Barry's approach is good, there is another one, more advanced and complicated. You can put your data object, (integer) key, and all bookkeeping bits in a single chunk of memory. Thus data locality will be improved and pressure on memory allocator will be less. Example, using boost::intrusive
:
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp>
#include <array>
using namespace boost::intrusive;
class Foo {
// bookkeeping bits
list_member_hook<> list_hook;
unordered_set_member_hook<> set_hook;
const int key;
// some payload...
public:
// there is even more options to configure container types
using list_type = list<Foo, member_hook<Foo, list_member_hook<>, &Foo::list_hook>>;
using set_type = unordered_set<Foo, member_hook<Foo, unordered_set_member_hook<>, &Foo::set_hook>>;
Foo(int key): key(key) {};
bool operator ==(const Foo &rhs) const {
return key == rhs.key;
}
friend std::size_t hash_value(const Foo &foo) {
return std::hash<int>()(foo.key);
}
};
class Bar {
Foo::list_type list;
std::array<Foo::set_type::bucket_type, 17> buckets;
Foo::set_type set{Foo::set_type::bucket_traits(buckets.data(), buckets.size())};
public:
template<typename... Args>
Foo &emplace(Args&&... args) {
auto foo = new Foo(std::forward<Args>(args)...);
// no more allocations
list.push_front(*foo);
set.insert(*foo);
return *foo;
}
void pop(const Foo &foo) {
set.erase(foo);
list.erase(list.iterator_to(foo));
// Lifetime management fun...
delete &foo;
}
};
int main() {
Bar bar;
auto &foo = bar.emplace(42);
bar.pop(foo);
}
Measure how good are both algorithms on your data. My idea may give you nothing but greater code complexity.
回答2:
If what you want to do is this:
Is it possible to look up an object by key, and then generate a list iterator from the reference of the object (or from the unordered_map generator?)
Then you can take advantage of the fact that list
iterators aren't invalidated on insertion or erase (unless you erase that particular iterator) and reorganize your structures like this:
std::list<Test> list;
std::unordered_map<int, std::list<Test>::iterator> map;
map.insert(std::make_pair(101,
list.insert(list.end(), t1)));
map.insert(std::make_pair(102,
list.insert(list.end(), t2)));
map.insert(std::make_pair(103,
list.insert(list.end(), t3)));
That way your map lookup gives you exactly what you want: a list
iterator
.
来源:https://stackoverflow.com/questions/28388487/using-both-map-and-list-for-same-objects