C++11之前:
通常使用push_back()向容器中加入一个右值元素(临时对象)的时候,首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样造成的问题是临时变量申请的资源就浪费。
C++11之后:
引入了右值引用,转移构造函数后,push_back()右值时就会调用构造函数和转移构造函数。
emplace_back:
源码:主要看有注释的地方,通过完美转发,最终到构造对象,找到对应的构造函数进行构造
template<class... _Valty>
// 可以看出传入的时完美转发的参数
decltype(auto) emplace_back(_Valty&&... _Val)
{ // insert by perfectly forwarding into element at end, provide strong guarantee
if (_Has_unused_capacity())
{
return (_Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...));
}
//在调用重新构造时,将参数再完美转发出去
_Ty& _Result = *_Emplace_reallocate(this->_Mylast(), _STD forward<_Valty>(_Val)...);
#if _HAS_CXX17
return (_Result);
#else /* ^^^ _HAS_CXX17 ^^^ // vvv !_HAS_CXX17 vvv */
(void)_Result;
#endif /* _HAS_CXX17 */
}
template<class... _Valty>
pointer _Emplace_reallocate(const pointer _Whereptr, _Valty&&... _Val)
{ // reallocate and insert by perfectly forwarding _Val at _Whereptr
// pre: !_Has_unused_capacity()
const size_type _Whereoff = static_cast<size_type>(_Whereptr - this->_Myfirst());
_Alty& _Al = this->_Getal();
const size_type _Oldsize = size();
if (_Oldsize == max_size())
{
_Xlength();
}
const size_type _Newsize = _Oldsize + 1;
const size_type _Newcapacity = _Calculate_growth(_Newsize);
const pointer _Newvec = _Al.allocate(_Newcapacity);
const pointer _Constructed_last = _Newvec + _Whereoff + 1;
pointer _Constructed_first = _Constructed_last;
_TRY_BEGIN
// 接收完美转发后的参数,根据参数构造对象
_Alty_traits::construct(_Al, _Unfancy(_Newvec + _Whereoff), _STD forward<_Valty>(_Val)...);
_Constructed_first = _Newvec + _Whereoff;
if (_Whereptr == this->_Mylast())
{ // at back, provide strong guarantee
_Umove_if_noexcept(this->_Myfirst(), this->_Mylast(), _Newvec);
}
else
{ // provide basic guarantee
_Umove(this->_Myfirst(), _Whereptr, _Newvec);
_Constructed_first = _Newvec;
_Umove(_Whereptr, this->_Mylast(), _Newvec + _Whereoff + 1);
}
_CATCH_ALL
_Destroy(_Constructed_first, _Constructed_last);
_Al.deallocate(_Newvec, _Newcapacity);
_RERAISE;
_CATCH_END
_Change_array(_Newvec, _Newsize, _Newcapacity);
return (this->_Myfirst() + _Whereoff);
}
template<class _Objty,
class... _Types>
static void construct(_Alloc&, _Objty * const _Ptr, _Types&&... _Args)
{ // construct _Objty(_Types...) at _Ptr
//最终传入的参数类型在这里使用,根据完美转发规则,传入的时什么类型参数,然后匹配其对象对应的构造函数进行对象构造。如果是一系列参数,就调用自定义的构造函数,如果是左值,就调用拷贝构造,如果是右值,就调用移动构造。若没有移动构造,就调用拷贝构造
::new (const_cast<void *>(static_cast<const volatile void *>(_Ptr)))
_Objty(_STD forward<_Types>(_Args)...);
}
结论:
- 对于传入的是对象构造参数(右值),empalce_pack内部是使用的完美转发,在构造对象时是进行原地构造,直接调用对象的构造函数进行构造,并存入容器中,减少push_back的临时对象构造和析构过程。
- 对于传入的是对象(左值),empalce_pack内部是使用的完美转发,在构造对象时调用其拷贝构造函数进行构造并存入容器中
- 对于传入的时对象右值(类似std::move()转换后的对象),empalce_pack内部是使用的完美转发,在构造对象时调用其移动构造函数进行构造并存入容器中。若没有移动构造,则调用拷贝构造创建对象并存入容器中。
push_back:
//可以看到,里面都是调用的emplace_back函数
void push_back(const _Ty& _Val)
{ // insert element at end, provide strong guarantee
emplace_back(_Val);
}
void push_back(_Ty&& _Val)
{ // insert by moving into element at end, provide strong guarantee
emplace_back(_STD move(_Val));
}
结论:
- 传入的是左值,根据emplace_back的完美转发原则,会调用拷贝构造函数进行元素添加
- 传入的是右值,同理根据完美转发原则,会调用移动构造函数进行构造,如果没有移动构造,则调用拷贝构造函数进行构造,并进行元素添加
代码测试:
#include <iostream>
#include <vector>
#include <string>
struct Persion
{
std::string name;
std::string country;
int year;
Persion(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";
}
Persion(const Persion& other)
: name(std::move(other.name)), country(std::move(other.country)), year(other.year)
{
std::cout << "I am being copy constructed.\n";
}
Persion(Persion&& other)
: name(std::move(other.name)), country(std::move(other.country)), year(other.year)
{
std::cout << "I am being moved.\n";
}
Persion& operator=(const Persion& other);
};
int main() {
std::vector<Persion> elections;
std::cout << "emplace_back:\n";
elections.emplace_back("person1", "South Africa", 1991); //没有类的创建
std::vector<Persion> reElections;
std::cout << "\npush_back:\n";
Persion test("person12", "the USA", 1992);
reElections.push_back(test);
reElections.push_back(std::move(test));
system("pause");
return 0;
}