一篇文章搞懂 push_back 和 emplace_back 的区别

这篇具有很好参考价值的文章主要介绍了一篇文章搞懂 push_back 和 emplace_back 的区别。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

push_back 和 emplace_back

本来以为自己对 push_back 和 emplace_back 的理解还行,直到我室友伦伦问了一个关于 push_back 和 emplace_back 的问题。死去的 modern effective c++ 记忆又开始攻击我…因此,我痛定思痛,在阅读了大量文献之后写下本文。

(全篇 1200 字阅读大约需要 8 分钟)

撰写本文出于两点原因:

  1. 该问题在面试频频出现
  2. 网上众说纷纭,缺乏系统全面的解释

什么是 push_back?什么是 emplace_back?

push_back

我们在使用 STL 容器时,以 std::vector 为例,经常会用到 push_back 向数组尾部添加一个新的元素。push_back 的作用:向容器尾部添加一个新的元素。

如果我们熟悉 c++ 模板编程,并且对底层实现有兴趣不妨看看它的定义:(没兴趣可以直接跳过)

_CONSTEXPR20 void push_back(const _Ty& _Val) { // insert element at end, provide strong guarantee
    _Emplace_one_at_back(_Val);
}

_CONSTEXPR20 void push_back(_Ty&& _Val) {
    // insert by moving into element at end, provide strong guarantee
    _Emplace_one_at_back(_STD move(_Val));
}
  • 除了 vector<bool> 之外,push_back 函数只存在上述两种重载版本。
  • 上述两种版本分别代表传入 左值和右值(由于篇幅问题本文不作讨论)。
  • 我们传入的类型必须是 _Ty

什么是 _Ty:在使用模板容器时传入到模板中的类型,比如 vector<int> 那么 _Ty 将会是 int。

emplace_back

emplace_back 在功能上和 push_back 没有区别,在使用上你完全可以放心地把 push_back 替换成 emplace_back(不可逆)。

emplace_back 的定义稍微复杂一些:

template <class... _Valty>
_CONSTEXPR20 decltype(auto) emplace_back(_Valty&&... _Val) {
    _Ty& _Result = _Emplace_one_at_back(_STD forward<_Valty>(_Val)...);
    return _Result;
}
  • 除了 vector<bool> 之外,emplace_back 函数只存在上述一种版本。

  • 可以看到与 push_back 不同的是,它接受可变参数(由于篇幅不作展开,有兴趣可以了解 universal reference, variadic arguments)。

  • 内部实现上两者没有区别,都调用了 _Emplace_one_at_back() 函数。

为什么需要 emplace_back?

这个时候有人就要问了,明明 push_back 就可以实现功能了为什么要整出一个 emplace_back? 不妨一起看看下面这个例子:

int main() {
    std::vector<std::string> sentences;
    sentences.push_back("hello, world");
    return 0;
}

// compile success!

上述代码通过编译,一切看起来万事大吉,但是存在一个隐患:从源码上可以看到 push_back 只接受 _Ty 类型(也就是 std::string),但是 “hello, world” 是 char [13] 类型,显然不满足条件,为啥可以编译通过,这是因为类的隐式转换(见附录)。因此上述代码实际上等价于:

int main() {
    std::vector<std::string> sentences;
    sentences.push_back(std::string("hello, world"));
    return 0;
}

可以看到,上述代码其实在 main 函数里构造了一个临时的 std::string 对象,这是我们不想要看到的(试想某个对象的构造非常消耗时间)。如果利用 emplace_back 就可以完美解决这个问题:

int main() {
    std::vector<std::string> sentences;
    sentences.emplace_back("hello, world");
    return 0;
}

这时我们不再需要隐式转换,因为 emplace_back 接受可变参数,因此直接传入到 emplace_back 函数中进行构造(接下来的操作和 push_back 如出一辙)。

最后看一个综合的例子:

class Person
{
    string name;

public:
    Person(const char *p)
    {
        cout << "construct" << endl;
    }

    Person(const Person &p)
    {
        cout << "Person(const Person&)" << endl;
    }
};

int main()
{
    std::vector<Person> persons;
    persons.reserve(2);
    cout << "---------emplace back---------" << endl;
    persons.emplace_back("John");
    cout << "---------push back---------" << endl;
    persons.push_back("mike");
    return 0;
}

// Output:
// ---------emplace back---------
// construct
// ---------push back---------
// construct
// Person(const Person&)

可以看到 emplace_back 只会调用一次构造函数,不会在原地构造完之后再次调用复制构造函数。

emplace_back 的优势

  • 可以避免创建临时对象,造成性能损失

后记

尽情地把 push_back 替换为 emplace_back 吧~

虽然 emplace_back 仿佛明显优于 push_back,但是这并不意味这 emplace 就一定优于 insert,因为具体的对象类型,和容器内部的组织形式决定了其有效性。因此,在使用过程中我们还需对 emplace 替换 insert 多加考虑。(可以参考 《modern effective c++》)

Appendix

类的隐式转换:某个类存在单个参数的构造函数,那么我们可以将该类型的参数隐式转换为一个该类对象。比如:

class Person
{
    string name;

public:
    Person(const char *p) {
        for (int i = 0; i < strlen(p); ++i)
        {
            name.push_back(p[i]);
        }
    }
    string getName() {
        return name;
    }
};

int main() {
    Person mike = "mike";
    cout << mike.getName() << endl;
    return 0;
}

// output:
// mike

如果我们不想要这种隐式转换出现,可以用 explicit 来修饰构造函数:文章来源地址https://www.toymoban.com/news/detail-477676.html

class Person
{
    string name;

public:
    explicit Person(char *p) {
        for (int i = 0; i < strlen(p); ++i)
        {
            name.push_back(p[i]);
        }
    }
    string getName() {
        return name;
    }
};

int main() {
    Person mike = "mike";
    cout << mike.getName() << endl;
    return 0;
}

// compile error
// vector_push_back_test.cpp:62:19: error:conversion from ‘const char [5]’ to non-scalar type ‘Person’ requested

到了这里,关于一篇文章搞懂 push_back 和 emplace_back 的区别的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一篇文章搞懂华为的ACL

    随着网络的飞速发展,网络安全问题日益突出。访问控制列表 (ACL, Access Control List) 可以通过对网络中报文流的精确识别,与其他技术结合,达到控制网络访问行为、防止网络攻击和提高网络带宽利用率的目的,从而切实保障网络环境的安全性和网络服务质量的可靠性。 访问

    2024年02月06日
    浏览(45)
  • 一篇文章带你搞懂前端Cookie

    浏览器Cookie相信各位点进这篇文章的小伙伴应该不陌生了,它是前端领域中一个非常重要的内容,当然也是面试的一个考点,不知道各位小伙伴是否真正掌握了Cookie呢?当然没有掌握也是没有关系的,可以跟着小编的脚步一起来学习一下前端Cookie,没有熟练掌握的小伙伴看完这

    2024年02月04日
    浏览(46)
  • 搞懂TVS管,有这篇文章就够了

    摘要:本文主要介绍TVS的工作原理、关键参数和选型。 TVS(Transient Voltage Suppressors,瞬态电压抑制器)又称雪崩击穿二极管,是一种高效电路保护器件,主要是保护电路不受瞬态高压尖峰脉冲(静电或雷击浪涌)的冲击。 TVS是采用半导体工艺制成的单个PN结或多个PN结集成的器件,

    2023年04月08日
    浏览(54)
  • 一篇文章搞懂前端sso需要做什么

    父域 Cookie 认证中心 LocalStorage 跨域 一般情况下,用户的登录状态是记录在 Session 中的,要实现共享登录状态,就要先共享 Session,但是由于不同的应用系统有着不同的域名,尽管 Session 共享了,但是由于 SessionId 是往往保存在浏览器 Cookie 中的,因此存在作用域的限制,无法

    2024年02月20日
    浏览(47)
  • 一篇文章带你搞懂stm32工程文件

    本文以stm32f4为例,讲解stm32标准库工程中各个文件的作用,学艺不精,如有错误,望大家私信或评论指出。 先看思维导图 startup_stm32f427xx.s  该文件是stm32的启动文件,由汇编语言编写,主要是做stm32上电时的配置设置(如堆栈指针,时钟数)并跳转到main函数中,执行c代码。

    2024年02月21日
    浏览(53)
  • 详细解释C++中.push_back的用法

    在 C++ 中, push_back 是一个用于在容器的末尾添加元素的成员函数,常用于向容器(例如向量、列表、队列等)添加新的元素。对于标准库中的容器, push_back 主要用于 std::vector 和 std::deque 。 以下是 push_back 的基本用法和解释: 在这个例子中, push_back 用于将整数元素添加到

    2024年03月15日
    浏览(31)
  • 什么是区块链?一篇文章搞懂区块链本质

    鉴于我对区块链的关注,以及很多关注我的朋友们,并不是很清楚区块链的本质和潜力点,所以今天在地铁里疏离了一下,并分享给大家。 你见过钱吗?我相信你是没见过真正的钱的。 这是钱吗? 这是“钞票”,而不是钱!这不是抠字眼,且听我细细道来。 别人管你借钱的

    2024年01月22日
    浏览(45)
  • Unity/C#------委托与事件(一篇文章彻底搞懂...)

            所有的代码语言创造者母语都是英语,我们从英语翻译到中文的过程中难免会存在一些不太能还原本意的词,比如我之前一直不理解构造函数和析构函数,只知道这俩货作用相反,直到我看到了它的英文意思,Construstor/Distructor,我才彻底理解了他们的作用。      

    2024年02月06日
    浏览(50)
  • 一篇文章让你搞懂自定义类型-----结构体

    结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量 例如描述一个学生 在声明结构的时候,可以不完全的声明 比如 上面的两个结构在声明的时候省略掉了结构体标签(tag) 那么问题来了 警告: 编译器会把上面的两个声明当成完全不同的两个

    2024年02月16日
    浏览(53)
  • 一篇文章带你搞懂GIT、Github、Gitee

    本文介绍了GIt,GitHub,Gitee的使用,与IDEA的Git配置,跟着文章来做你很快就能学会操作Git,利用其进行版本控制与代码托管,学习Git的使用、Git常用命令、Git分支,分支是团队协作的基础,介绍了团队内,外协作和Github远程仓库的操作、使用IDEA中的Git、IDEA中GIt的使用、在I

    2023年04月19日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包