面试题: C++ vector 中的 push_back() 和 emplace_back() 的区别、以及使用场景

旅行   2024-11-20 08:08   广东  

欢迎关注本公众号,专注面试题拆解

分享一套视频课程《C++百万并发服务器开发》,有需要的加我微信获取:fb964919126


面试题:C++ vector 中的 push_back() 和 emplace_back() 的区别、以及使用场景。

这也是一道高频面试题,基本校招必问题,需要掌握。


基本概念解析

push_back() 的工作原理

push_back() 是 vector 最基础的插入操作,它会在 vector 末尾插入一个元素。当我们调用 push_back() 时,它需要一个已经构造好的对象作为参数。这意味着在插入过程中可能会发生对象的拷贝或移动操作。如果传入的是左值,就会调用拷贝构造函数;如果传入的是右值,则会调用移动构造函数。

emplace_back() 的工作原理


emplace_back() 是 C++11 引入的新特性,它的设计目的是提高容器插入元素的效率。与 push_back() 不同,emplace_back() 可以直接接收对象构造函数的参数,然后在容器的内存空间中直接构造对象。这样就避免了临时对象的构造和销毁过程,理论上能够提供更好的性能。

特性push_back()emplace_back()
基本功能将一个已构造的对象复制或移动到容器在容器中完成直接构造对象
是否需要构造临时对象是,需要先构造对象再插入否,直接在容器内构造对象
构造与性能可能涉及一次复制或移动(依赖于对象类型和语义)无需拷贝或移动,直接调用构造函数
参数传递需要完整的对象作为参数直接传递构造对象的参数

实现机制

push_back()

收到一个对象的副本,或者右值引用。
如果格式化是一个临时对象(右值),且类支持移动语义,则调用其移动构造函数。
如果确定左值,则需要调用拷贝构造函数。

#include <vector>#include <iostream>class A {public:    A(int x) : x(x) { std::cout << "Constructed A\n"; }    A(const A& other) { std::cout << "Copy Constructed A\n"; }    A(A&& other) noexcept { std::cout << "Move Constructed A\n"; }    int x;};int main() {    std::vector<A> vec;    A a(10);             // Constructed A    vec.push_back(a);    // Copy Constructed A    vec.push_back(A(20));// Constructed A, Move Constructed A    return 0;}

实际运行发现会多输出一次Move,原因是因为vector发生了扩容.

emplace_back()

不需要构造一个临时对象,而是直接在容器内调用构造函数构造对象。
可以减少临时对象的创建、拷贝或移动,从而提升性能。

#include <vector>#include <iostream>
class A {public: A(int x) : x(x) { std::cout << "Constructed A\n"; } A(const A& other) { std::cout << "Copy Constructed A\n"; } A(A&& other) noexcept { std::cout << "Move Constructed A\n"; } int x;};
int main() { std::vector<A> vec; vec.emplace_back(10); // Constructed A (直接在容器中构造,无拷贝或移动) return 0;}


性能对比


push_back():

首先构造一个临时对象

将这个临时对象拷贝或移动到 vector 的存储空间

销毁临时对象

这个过程中涉及多次构造和销毁操作


emplace_back():

直接在vector 的存储空间构造对象,

避免了临时对象的创建和销毁,减少了内存操作。


emplace_back()通常比较push_back()高效,尤其是在以下情况下:

对象的构造和复制成本更高(例如复杂的自定义类型)。

构造临时对象需要额外的计算时间。


使用场景


适合使用 push_back() 的情况

1、当对象已经存在

std::string str = "Hello";std::vector<std::string> vec;vec.push_back(str);  // 这里使用 push_back 更直观

在这种情况下,对象已经构造完成,使用 push_back() 更符合直觉。

2、需要多次使用同一个对象时

MyClass obj(1, 2);vec1.push_back(obj);vec2.push_back(obj);map1[key] = obj;

当需要在多个地方使用同一个对象时,我个人认为先构造对象再使用 push_back() 是更好的选择。

适合使用 emplace_back() 的情况

1、避免临时对象

std::vector<A> vec;vec.emplace_back(10); // 直接构造,无需临时对象

2、复杂构造函数

std::vector<std::pair<int, std::string>> vec;vec.emplace_back(1"example"); // 直接构造 std::pair<int, std::string>

3、对象构造参数较多时

struct Complex {    Complex(int a, int b, int c, std::string d) {}};std::vector<Complex> vec;// emplace_back 可以直接传递构造参数vec.emplace_back(1, 2, 3, "test");// push_back 需要显式构造对象vec.push_back(Complex(1, 2, 3, "test"));


总结

性能考虑

对于简单类型(如 int),两者性能差异很小

对于复杂对象,emplace_back() 通常能提供更好的性能

具体性能差异需要通过实际测试来确定。


使用建议

默认优先使用 push_back(),代码更清晰直观

在性能关键的场景,考虑使用 emplace_back()

对于复杂对象的直接构造,优先使用 emplace_back()

end


CppPlayer 



关注,回复【电子书】珍藏CPP电子书资料赠送

精彩文章合集

专题推荐

【专辑】计算机网络真题拆解
【专辑】大厂最新真题
【专辑】C/C++面试真题拆解

CppPlayer
一个专注面试题拆解的公众号
 最新文章