我的std::unordered_map
带有 value_type 但没有默认构造函数,因此我无法执行以下操作
auto k = get_key();
auto& v = my_map[k];
我最终写了一个辅助函数
value_type& get_value(key_type& key)
{
return std::get<0>(my_map.emplace(
std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(args_to_construct_value)
))->second;
}
但是性能明显更差(即value_type的构造函数显示为perf),而不是以下版本。
value_type& get_value(key_type& key)
{
auto it = my_map.find(key);
if (it == my_map.end())
return std::get<0>(my_map.emplace(
std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(args_to_construct_value)
))->second;
else
return it->second;
}
我从std::unordered_map::emplace object creation读到,需要构建对象以查看是否存在。但是,emplace正在检查地图中返回之前是否存在此键值对。
我以错误的方式使用安魂?我应该遵循更好的模式:
由于
答案 0 :(得分:4)
不幸的是,您的代码最适合当前的标准库。
问题是emplace
操作旨在避免复制,而不是为了避免不必要的映射类型构造。实际上,会发生的是实现分配并构造一个节点,其中包含映射value_type
,即pair<const Key, T>
,然后然后散列密钥以确定构造的节点是否可以被连接到容器中;如果发生这种情况,则会删除该节点。
只要hash
和equal_to
不是太贵,您的代码就不应该做太多额外的工作。
另一种方法是使用自定义分配器来拦截映射类型的0参数构造;问题是检测这种结构非常繁琐:
#include <unordered_map>
#include <iostream>
using Key = int;
struct D {
D() = delete;
D(D const&) = delete;
D(D&&) = delete;
D(std::string x, int y) { std::cout << "D(" << x << ", " << y << ")\n"; }
};
template<typename T>
struct A {
using value_type = T;
using pointer = T*;
using const_pointer = T const*;
using reference = T&;
using const_reference = T const&;
template<typename U> struct rebind { using other = A<U>; };
value_type* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
void deallocate(T* c, std::size_t n) { std::allocator<T>().deallocate(c, n); }
template<class C, class...Args> void construct(C* c, Args&&... args) { std::allocator<T>().construct(c, std::forward<Args>(args)...); }
template<class C> void destroy(C* c) { std::allocator<T>().destroy(c); }
std::string x; int y;
A(std::string x, int y): x(std::move(x)), y(y) {}
template<typename U> A(A<U> const& other): x(other.x), y(other.y) {}
template<class C, class...A> void construct(C* c, std::piecewise_construct_t pc, std::tuple<A...> a, std::tuple<>) {
::new((void*)c)C(pc, a, std::tie(x, y)); }
};
int main() {
using UM = std::unordered_map<Key, D, std::hash<Key>, std::equal_to<Key>, A<std::pair<const Key, D>>>;
UM um(0, UM::hasher(), UM::key_equal(), UM::allocator_type("hello", 42));
um[5];
}
答案 1 :(得分:3)
您可以使用boost::optional<T>
以便能够默认构建映射类型,然后稍后为其分配初始化的T
。
#include <cassert>
#include <unordered_map>
#include <boost/optional.hpp>
struct MappedType
{
explicit MappedType(int) {}
};
int main()
{
std::unordered_map<int, boost::optional<MappedType>> map;
boost::optional<MappedType>& opt = map[0];
assert(!opt.is_initialized());
opt = MappedType(2);
assert(opt.is_initialized());
MappedType& v = opt.get();
}
答案 2 :(得分:1)
你在任何一个实现中都没有做错。 emplace
仅比find
做更多的工作,尤其是当您的unordered_map
中已存在密钥时。
如果您的get_value
辅助函数主要接收重复项,那么每次调用emplace
都会导致您观察到的性能热点。