C++11之前: 通常使用push_back()向容器中加入一个右值元素(临时对象)的时候,首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样造成的问题是临时变量申请的资源就浪费。 C++11之后: 引入了右值引用,转移构造函数后,push_back()右值时就会调用构造函数和转移构造函数。
emplace_back: 源码:主要看有注释的地方,通过完美转发,最终到构造对象,找到对应的构造函数进行构造
template<class... _Valty>
decltype(auto) emplace_back(_Valty&&... _Val)
{
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
(void)_Result;
#endif
}
template<class... _Valty>
pointer _Emplace_reallocate(const pointer _Whereptr, _Valty&&... _Val)
{
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())
{
_Umove_if_noexcept(this->_Myfirst(), this->_Mylast(), _Newvec);
}
else
{
_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)
{
::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:
void push_back(const _Ty& _Val)
{
emplace_back(_Val);
}
void push_back(_Ty&& _Val)
{
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;
}
|