每周技巧 #112:emplace 与 push_back
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #112: emplace vs. push_back。
原文最初作为 totw/112 发布于 2016 年 2 月 25 日。
作者:Geoff Romer
修订于 2017-08-30。
“我们越少使用自己的力量,它就会越强大。”— Thomas Jefferson
你可能已经知道(如果不知道,见 TotW 65),C++11 引入了一种强大的新方式向容器插入项目:emplace 方法。它们允许你使用对象的任意构造函数,在容器内部就地构造对象。这包括移动构造函数和拷贝构造函数,所以结果是:任何时候只要你能使用 push 或 insert 方法,你都可以改用 emplace 方法,而不需要其他改动:
|
|
这引出了一个显然的问题:你应该用哪个?我们是不是应该干脆抛弃 push_back() 和 insert(),一直使用 emplace 方法?
我用另一个问题来回答这个问题:下面两行代码做什么?
|
|
第一行相当直接:它把数字 1048576 添加到 vector 末尾。然而第二行没那么清楚。不知道 vector 的类型,我们就不知道它调用了什么构造函数,因此无法真正说出这行在做什么。例如,如果 vec2 是 std::vector<int>,那这一行和第一行一样,只是把 1048576 添加到末尾;但如果 vec2 是 std::vector<std::vector<int>>,第二行会构造一个包含一百多万个元素的 vector,过程中分配数 MB 内存。
因此,如果你可以在相同参数下选择 push_back() 或 emplace_back(),选择 push_back() 往往会让代码更可读,因为 push_back() 更具体地表达了你的意图。选择 push_back() 也更安全:假设你有一个 std::vector<std::vector<int>>,想把一个数字追加到第一个 vector 末尾,但不小心忘了下标。如果你写 my_vec.push_back(2<<20),会得到编译错误,并很快发现问题。另一方面,如果你写 my_vec.emplace_back(2<<20),代码会编译,直到运行时你才会发现问题。
确实,当涉及隐式转换时,emplace_back() 可能比 push_back() 稍快。例如,在开头的代码中,my_vec.push_back("foo") 会从字符串字面量构造一个临时 string,然后把该 string 移入容器;而 my_vec.emplace_back("foo") 直接在容器中构造 string,避免了额外移动。对于更昂贵的类型,这可能是使用 emplace_back() 而不是 push_back() 的理由,尽管它有可读性和安全性成本;但也可能不是。性能差异经常并不重要。一如既往,经验法则是:除非性能收益大到能在应用基准测试中体现出来,否则应该避免让代码更不安全或更不清晰的“优化”。
所以一般来说,如果 push_back() 和 emplace_back() 能使用相同参数工作,你应该优先使用 push_back();insert() 与 emplace() 也是如此。