c++ push_back()和emplace_back()区别

c++ push_back()和emplace_back()区别

References

  1. C++中push_back和emplace_back的区别
  2. push_back v.s. emplace_back

一、源码分析

(1)push_back()定义

// stl_vector.h

template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class vector : protected _Vector_base<_Tp, _Alloc>
{
      ...
      void
      push_back(const value_type& __x)
      {
	if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
	  {
	    _GLIBCXX_ASAN_ANNOTATE_GROW(1);
	    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
				     __x);
	    ++this->_M_impl._M_finish;
	    _GLIBCXX_ASAN_ANNOTATE_GREW(1);
	  }
	else
	  _M_realloc_insert(end(), __x);
      }

#if __cplusplus >= 201103L
      void
      push_back(value_type&& __x)
      { emplace_back(std::move(__x)); }
}
  1. 需传入对应类型的对象(隐式构造的情况除外)
  2. C++11以及之后版本,传入右值需调用移动构造函数(若未定义,则调用拷贝构造函数)。

类的构造函数未添加explicit且只接受一个参数的情况,也可以传入单个参数进行构造。但涉及另一知识点:如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,有时我们把这种构造函数称作转换构造函数(converting constructor)。

  1. push_back传入参数不涉及类型推断,以下x为rvalue reference。

若没有一个特定的std::vector实例化, push_back是不存在的。实例化的类型完全决定了push_back的声明。不涉及类型推断。


template <class T, class Allocator = allocator<T> >
class vector {
public:
    ...
    void push_back(T&& x);       // fully specified parameter type => no type deduction;
    ...                          // && is rvalue reference
};

(2)emplace_back()定义

#if __cplusplus >= 201103L
  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)
	  {
	    _GLIBCXX_ASAN_ANNOTATE_GROW(1);
	    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
				     std::forward<_Args>(__args)...);
	    ++this->_M_impl._M_finish;
	    _GLIBCXX_ASAN_ANNOTATE_GREW(1);
	  }
	else
	  _M_realloc_insert(end(), std::forward<_Args>(__args)...);
#if __cplusplus > 201402L
	return back();
#endif
      }
#endif

  1. emplace_back既可以传入相应类型对象,还可以传入元素的类构造函数的参数列表进行原地构造。参数列表通过std::forward进行完美转发。
  2. emplace_back的传入参数需要类型推断,以下Args为Universal Reference.emplace_back类型参数Args独立于vector模板类型参数T,所以Args类型在每次被调用的时候都必须被推断。
template <class T, class Allocator = allocator<T> >
class vector {
public:
    ...
    template <class... Args>
    void emplace_back(Args&&... args); // deduced parameter types => type deduction;
    ...                                // && is universal references
};

二、对比push_back和emplace_back

(1)定义测试类


class Foo{
private:
    int a;
public:
    Foo(int a): a(a){
        cout<<"Foo Constructor "<<a<<endl;
    }
    Foo(){
        cout<<"Default Foo Constructor"<<endl;
    }
    ~Foo() { cout << "Foo Destructor..." <<a<<endl; }
    Foo(const Foo& foo){
        cout<<"copy constructor"<<endl;
    }
    Foo(Foo&& foo){
        cout<<"move constructor"<<endl;
    }
};

(2)测试用例

  1. push_back拷贝构造,emplace_back原地构造.
#include <iostream>
#include <vector>
using namespace std;
int main() {
    cout<<endl<<"push_back begin"<<endl<<endl;
    vector<Foo> vec1;
    Foo foo1(1);
    vec1.push_back(foo1);
    cout<<endl<<"push_back end"<<endl<<endl;

    cout<<endl<<"emplace_back begin"<<endl<<endl;
    vector<Foo> vec2;
    vec2.emplace_back(2);
    cout<<endl<<"emplace_back end"<<endl<<endl;
}

输出:

push_back begin

Foo Constructor 1
copy constructor //调用拷贝构造

push_back end


emplace_back begin

Foo Constructor 2  //vector上原地构造,无需拷贝,也无需移动

emplace_back end

Foo Destructor...2
Foo Destructor...1
Foo Destructor...0
  1. 拷贝构造函数和移动构造函数均已定义,用push_back/emplace_back向容器中加入左值元素,均将调用拷贝构造函数拷贝对象到容器中。
#include <iostream>
#include <vector>
using namespace std;
int main() {
    cout<<endl<<"push_back begin"<<endl<<endl;
    vector<Foo> vec1;
    Foo foo1(1);
    vec1.push_back(foo1);
    cout<<endl<<"push_back end"<<endl<<endl;

    cout<<endl<<"emplace_back begin"<<endl<<endl;
    vector<Foo> vec2;
    Foo foo2(2);
    vec2.emplace_back(foo2);
    cout<<endl<<"emplace_back end"<<endl<<endl;
}

输出:

push_back begin

Foo Constructor 1
copy constructor

push_back end


emplace_back begin

Foo Constructor 2
copy constructor

emplace_back end

Foo Destructor...2 //左值离开作用域销毁
Foo Destructor...0
Foo Destructor...1 //左值离开作用域销毁
Foo Destructor...0

  1. 拷贝构造函数和移动构造函数均定义,push_back/emplace_back向容器中加入右值元素, 那么均会调用移动构造函数在vector上进行构造。
  • 加入显式构造的右值元素
vec1.push_back(Foo(1));
vec1.emplace_back(Foo(1));
  • 通过std::move()转化而成的右值元素
Foo foo1;
vec1.push_back(std::move(foo1))
Foo foo2;
vec1.emplace_back(std::move(foo2));
  • 测试
#include <iostream>
#include <vector>
using namespace std;
int main() {
    cout<<endl<<"push_back begin"<<endl<<endl;
    vector<Foo> vec1;
    vec1.push_back(Foo(1));
    cout<<endl<<"push_back end"<<endl<<endl;

    cout<<endl<<"emplace_back begin"<<endl<<endl;
    vector<Foo> vec2;
    vec2.emplace_back(Foo(2));
    cout<<endl<<"emplace_back end"<<endl<<endl;
}

输出:

push_back begin

Foo Constructor 1
move constructor
Foo Destructor...1  //右值立马销毁

push_back end


emplace_back begin

Foo Constructor 2  
move constructor
Foo Destructor...2  //右值立马销毁

emplace_back end

Foo Destructor...0
Foo Destructor...0
  1. 若仅定义拷贝构造函数,未定义移动构造函数,push_back/emplace_back向容器中加入右值元素, 那么均会调用拷贝构造函数构造对象在vector上。

#include <iostream>
#include <vector>
using namespace std;
class Foo{
private:
    int a;
public:
    Foo(int a): a(a){
        cout<<"Foo Constructor "<<a<<endl;
    }
    Foo(){
        cout<<"Default Foo Constructor"<<endl;
    }
    ~Foo() { cout << "Foo Destructor..." <<a<<endl; }
    Foo(const Foo& foo){
        cout<<"copy constructor"<<endl;
    }
    // Foo(const Foo&& foo){
    //     cout<<"move constructor"<<endl;
    // }
    // Foo(Foo&& foo)=delete;
};

int main() {
    cout<<endl<<"push_back begin"<<endl<<endl;
    vector<Foo> vec1;
    vec1.push_back(Foo(1));
    cout<<endl<<"push_back end"<<endl<<endl;

    cout<<endl<<"emplace_back begin"<<endl<<endl;
    vector<Foo> vec2;
    vec2.emplace_back(Foo(2));
    cout<<endl<<"emplace_back end"<<endl<<endl;
}

输出:

push_back begin

Foo Constructor 1
copy constructor
Foo Destructor...1  //右值立马销毁

push_back end


emplace_back begin

Foo Constructor 2
copy constructor
Foo Destructor...2  //右值立马销毁

emplace_back end

Foo Destructor...0
Foo Destructor...0

posted on 2022-03-02 21:47  七昂的技术之旅  阅读(460)  评论(0编辑  收藏  举报

导航