STL:emplace_back() 和 push_back 的区别

Posted 滔滔就是我hia

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL:emplace_back() 和 push_back 的区别相关的知识,希望对你有一定的参考价值。

性能问题:

在引入右值引用,转移构造函数,转移复制运算符之前,通常使用push_back()向容器中加入一个右值元素(临时对象)的时候,首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样造成的问题是临时变量申请的资源就浪费。

引入了右值引用,转移构造函数后,push_back()右值时就会调用构造函数和转移构造函数。

在这上面有进一步优化的空间就是使用emplace_back(),使用emplace_back()在容器尾部添加一个元素,这个元素原地构造,不需要触发拷贝构造和转移构造。而且调用形式更加简洁,直接根据参数初始化临时对象的成员。

#include <vector>
#include <string>
#include <iostream>

struct President
{
    std::string name;
    std::string country;
    int year;

    President(std::string p_name, std::string p_country, int p_year)
        : name(std::move(p_name)), country(std::move(p_country)), year(p_year)
    {
        std::cout << "I am being constructed.\\n";
    }
    President(const President& other)
        : name(std::move(other.name)), country(std::move(other.country)), year(other.year)
    {
        std::cout << "I am being copy constructed.\\n";
    }
    President(President&& other)
        : name(std::move(other.name)), country(std::move(other.country)), year(other.year)
    {
        std::cout << "I am being moved.\\n";
    }
    President& operator=(const President& other);
};

int main()
{
    std::vector<President> elections;
    std::cout << "emplace_back:\\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994); //没有类的创建

    std::vector<President> reElections;
    std::cout << "\\npush_back:\\n";
    reElections.push_back(President("Franklin Delano Roosevelt", "the USA", 1936));

    std::cout << "\\nContents:\\n";
    for (President const& president: elections) {
       std::cout << president.name << " was elected president of "
            << president.country << " in " << president.year << ".\\n";
    }
    for (President const& president: reElections) {
        std::cout << president.name << " was re-elected president of "
            << president.country << " in " << president.year << ".\\n";
    }

}

源码剖析:

push_back:

/**
 *  以下程序来自STL源码 bits/stl_vector.h
 *
 *  @brief  Add data to the end of the %vector.
 *  @param  __x  Data to be added.
 *
 *  This is a typical stack operation.  The function creates an
 *  element at the end of the %vector and assigns the given data
 *  to it.  Due to the nature of a %vector this operation can be
 *  done in constant time if the %vector has preallocated space
 *  available.
 */
void push_back(const value_type &__x) {
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
        // 首先判断容器满没满,如果没满那么就构造新的元素,然后插入新的元素
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                                 __x);
        ++this->_M_impl._M_finish; // 更新当前容器内元素数量
    } else
        // 如果满了,那么就重新申请空间,然后拷贝数据,接着插入新数据 __x
        _M_realloc_insert(end(), __x);
}

// 如果 C++ 版本为 C++11 及以上(也就是从 C++11 开始新加了这个方法),使用 emplace_back() 代替
#if __cplusplus >= 201103L
void push_back(value_type &&__x) {
    emplace_back(std::move(__x));
}
#endif

emplace_back:

/**
 *  以下程序来自STL源码 bits/vector.tcc
 */
template<typename _Tp, typename _Alloc>
template<typename... _Args>
#if __cplusplus > 201402L
typename vector<_Tp, _Alloc>::reference
#else
void
#endif
vector<_Tp, _Alloc>::emplace_back(_Args &&... __args) {
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
        // 同样判断容器是否满了,没满的话,执行构造函数,对元素进行构造,并执行类型转换
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                                 std::forward<_Args>(__args)...);
        ++this->_M_impl._M_finish; // 更新当前容器大小
    } else
        // 满了的话重新申请内存空间,将新的元素继续构造进来,并且进行类型转换
        _M_realloc_insert(end(), std::forward<_Args>(__args)...);
#if __cplusplus > 201402L
    return back(); // 在 C++14版本之后,添加返回值,返回最后一个元素的引用
#endif
}

#endif

emplace_back() 和 push_back() 主要区别:

_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                                 std::forward<_Args>(__args)...); // emplace_back()
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                                 __x);                            // push_back()

对于std::forward() 函数而言,本质上是一个类型转换函数:
在强制类型转换中,将参数 __t 传递给对应类 _Tp 的构造函数,然后调用了该类的构造函数从而完成对象创建过程。

因此,在 emplace_back() 函数中,是支持直接将构造函数所需的参数传递过去,然后构建一个新的对象出来,然后填充到容器尾部的。

以上是关于STL:emplace_back() 和 push_back 的区别的主要内容,如果未能解决你的问题,请参考以下文章

C语言 push和pop函数可以直接用吗?

什么是push ,pop

vector::push_back 和 string::push_back 之间的区别?

收到push时和点击push时加埋点需求

收到push时和点击push时加埋点需求

如何同时使用“web-push”和“fcm-push”节点包?