每周技巧 #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 方法。它们允许你使用对象的任意构造函数,在容器内部就地构造对象。这包括移动构造函数和拷贝构造函数,所以结果是:任何时候只要你能使用 pushinsert 方法,你都可以改用 emplace 方法,而不需要其他改动:

1
2
3
4
5
6
7
std::vector<string> my_vec;
my_vec.push_back("foo");     // 这没问题,所以……
my_vec.emplace_back("foo");  // 这也没问题,并且结果相同

std::set<string> my_set;
my_set.insert("foo");        // 这里也一样:任何 insert 调用
my_set.emplace("foo");       // 都可以重写成 emplace 调用。

这引出了一个显然的问题:你应该用哪个?我们是不是应该干脆抛弃 push_back()insert(),一直使用 emplace 方法?

我用另一个问题来回答这个问题:下面两行代码做什么?

1
2
vec1.push_back(1<<20);
vec2.emplace_back(1<<20);

第一行相当直接:它把数字 1048576 添加到 vector 末尾。然而第二行没那么清楚。不知道 vector 的类型,我们就不知道它调用了什么构造函数,因此无法真正说出这行在做什么。例如,如果 vec2std::vector<int>,那这一行和第一行一样,只是把 1048576 添加到末尾;但如果 vec2std::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() 也是如此。

每周技巧 #109:函数声明中有意义的 `const`

上一节

每周技巧 #116:在参数上保留引用

下一节


本节目录