try_emplace不会移动或拷贝参数,因为它仅在键不存在时才直接在容器内存中就地构造值对象,完全避免临时对象的创建与传递。

try_emplace 为什么不会移动或拷贝参数
try_emplace 的核心优势在于:它只在键不存在时才构造值对象,且直接在容器内存中就地构造,完全绕过临时对象的创建、移动或拷贝。而 insert(尤其是接受 std::pair 的重载)必须先构造一个临时 std::pair,再将其插入——哪怕键已存在,这个临时对象也白造了。
常见错误现象:insert({key, expensive_obj()}) 会无条件调用 expensive_obj(),即使 key 已存在;而 try_emplace(key, expensive_obj()) 中,expensive_obj() 只有在键缺失时才被求值。
- 使用场景:值类型构造开销大(如
std::string拼接、自定义类含资源分配)、或构造过程可能抛异常需避免冗余执行 - 注意:
try_emplace不接受std::pair,只接受可转发给Value构造函数的参数包 - 兼容性:C++17 起支持,老标准只能用
insert或emplace
insert 和 emplace 在 map 中的实际行为差异
insert 对已有键返回失败(pair 的 second 为 false),不修改原值;emplace 行为类似,但若键已存在,它仍会尝试构造 Value(只是丢弃),造成浪费;try_emplace 则彻底跳过构造——这是三者最本质的分水岭。
性能影响显著:对 std::map 插入重复键,emplace(1, big_vector) 每次都复制 big_vector,而 try_emplace(1, big_vector) 仅第一次构造。
立即学习“C++免费学习笔记(深入)”;
-
insert(std::make_pair(k, v)):强制拷贝/移动v,且std::make_pair可能推导出错误类型(如int&退化) -
insert({k, v}):隐式构造std::pair,同样触发v的拷贝/移动 -
emplace(k, args...):转发args...构造pair,但不检查键是否存在就构造——危险
什么情况下 try_emplace 反而更慢
当 Value 是 trivial 类型(如 int、double)或廉价可移动类型,且插入几乎总是成功(即键极少重复)时,try_emplace 的额外键查找开销(先查再决定是否构造)可能略高于直接 emplace。但这种差距通常在纳秒级,实际难以测出。
真正要警惕的是误用:比如把 try_emplace 当作“带默认值的下标访问”——它不会像 operator[] 那样在键不存在时默认构造 Value 并返回引用;它只插入,不提供后续修改的便捷入口。
- 错误写法:
m.try_emplace(k).first->second = new_val;—— 多此一举,且破坏原子性 - 正确替代:
auto [it, inserted] = m.try_emplace(k, init_val); if (!inserted) it->second = new_val; - 若需“存在则更新”,
operator[]或insert_or_assign(C++17)更合适
insert_or_assign 是不是比 try_emplace 更常用
insert_or_assign 的语义是“插入新键值对,或覆盖旧值”,它必然修改映射内容;而 try_emplace 的语义是“仅当键不存在时插入”,严格保持原有值不变。两者解决的问题根本不同。
容易踩的坑:用 insert_or_assign(k, expensive_computation()) 会导致每次调用都执行 expensive_computation(),无论键是否存在;而 try_emplace 或手动检查 + operator[] 才能规避。
- 需要覆盖逻辑 → 选
insert_or_assign - 需要幂等插入(不覆盖)→ 选
try_emplace - 需要插入后立即读写 →
operator[]最简洁,但会默认构造未存在的值
复杂点在于:没有银弹。键重复率、值构造成本、是否允许默认构造、线程安全要求——这些都会让最优选择浮动。别只盯着函数名,先想清楚你到底想表达什么语义。









