给出如下地图的地图:
std::map<unsigned int, std::map<std::string, MyBase*>> m_allMyObjects;
插入/添加/&#34; emplace&#34;最有效的方式是什么?给定unsigned int
和std::string
考虑优化的m_allMyObjects元素(在现代编译器上)?
然后检索元素的最有效方法是什么?
m_allMyObjects将来可能包含多达100个&#39; 000个元素。
答案 0 :(得分:3)
关于如何有效地插入地图的常识和民间传说(通常告诉你要避免operator[]
并且更喜欢闪亮的新emplace
)考虑构建和复制值的成本在地图中。在您的情况下,这些值是普通指针,几乎可以免费复制,并且编译器可以积极地优化复制指针。
另一方面,实际上你确实有一个昂贵的对象,即std::string
类型的 key 。您需要注意密钥的副本(移动或复制)以确定性能。显然,对于树查找,您已经需要字符串对象,即使您将其作为char *,因为没有插入函数通过键的类型进行模板化。这意味着要查找要插入的位置,可以使用一个特定的std :: string对象,但是一旦创建了映射节点,映射中的新std :: string对象就会从中进行复制初始化(可能已移动)。避免超出单一副本/移动的所有内容应该是您的目标。
示例时间!
#include <map>
#include <cstdio>
struct noisy {
noisy(int v) : val(v) {}
noisy(const noisy& src) : val(src.val) { std::puts("copy ctor"); }
noisy(noisy&& src) : val(src.val) { std::puts("move ctor"); }
noisy& operator=(const noisy& src)
{ val = src.val; std::puts("copy assign"); return *this; }
noisy& operator=(noisy&& src)
{ val = src.val; std::puts("move assign"); return *this; }
int val;
};
bool operator<(const noisy& a, const noisy& b)
{
return a.val < b.val;
}
int main(void)
{
std::map<noisy,int> m;
std::puts("Operator[]");
m[noisy(1)] = 3;
std::puts("insert/make_pair");
m.insert(std::make_pair(noisy(2), 3));
std::puts("insert/make_pair/ref");
m.insert(std::make_pair<noisy&&,int>(noisy(3), 3));
std::puts("insert/pair/ref");
m.insert(std::pair<noisy&&,int>(noisy(4), 3));
std::puts("emplace");
m.emplace(noisy(5), 3);
}
使用g ++ 4.9.1编译,-std = c ++ 11,-O2,结果为
Operator[]
move ctor
insert/make_pair
move ctor
move ctor
insert/make_pair/ref
move ctor
move ctor
insert/pair/ref
move ctor
emplace
move ctor
其中显示:避免创建包含密钥副本的中间对的所有内容!请注意,std :: make_pair永远不会创建包含引用的对,即使它可以通过引用获取参数!每当您传递包含密钥副本的对时,密钥都会被复制到对中,然后再复制到地图中。
MarkMB建议的表达式,即m[int_k][str_k] = ptr
,非常好,可能产生最佳代码。第一个索引(int_k)没有理由不使用[]
,因为如果索引尚未使用,想要默认构造的子映射,因此没有不必要的开销。正如我们所看到的,使用字符串索引可以通过单个副本消失,所以你没事。如果你能承受失去你的字符串,m[int_k][std::move(str_k)] = ptr
可能是一个胜利。正如开头所讨论的,使用emplace
代替[]
只是关于值,在您的情况下几乎可以自由处理。