C++11 为容器新增了 emplace
函数,它可以“就地”构造,减少了构造后再拷贝、转移的成本,提高了效率,而且用起来也很方便。
以vector为例,vector中提供了push_back
和emplace_back
两个方法,对于直接插入对象的情形,两者没有任何区别:
class Test
{
public:
Test(int) { cout << "Test(int)" << endl; }
Test(int, int) { cout << "Test(int, int)" << endl; }
Test(const Test&) { cout << "Test(const Test&)" << endl; }
Test(Test &&) { cout << "Test(Test &&)" << endl; }
~Test() { cout << "~Test()" << endl; }
};
int main()
{
vector<Test> v;
v.reserve(100);
Test t1(10);
cout << "============" << endl;
//直接插入对象,两个没有区别
v.push_back(t1);
v.emplace_back(t1);
cout << "============" << endl;
v.push_back(Test(20));
v.emplace_back(Test(20));
cout << "============" << endl;
v.push_back(20);
//v.push_back(30, 40);
cout << "============" << endl;
//给emplace传入Test对象构造所需要的参数,直接在容器底层构造对象
//省去了临时对象的构造析构
v.emplace_back(20);
v.emplace_back(30, 40);
cout << "============" << endl;
return 0;
}
来看一下输出结果:
可以看到在使用emplace_back(20)
时省去了临时对象的构造,是根据参数直接在容器底层构造函数,提高了效率。之所以说提高了效率,是因为容器里存储的都是元素的拷贝,副本,使用emplace
减少了临时对象的构造及拷贝,自然提高了效率。而且,在使用emplace_back
时还可以“像函数调用”一样直接传入参数,无需添加构造,比如v.emplace_back(30, 40);
那emplace_back
底层是如何实现的呢?
这里仿写一个vector
:
//容器的空间配置器
template<typename T>
struct CustomAllocator
{
T* allocate(size_t size)
{
return (T*)malloc(size * sizeof(T));
}
//deallocate
void deallocate(T* ptr)
{
free(ptr);
}
template<typename... Types>
void construct(T *ptr, Types&&... args)
{
new (ptr) T(std::forward<Types>(args)...);
}
void destroy(T *ptr)
{
}
};
template<typename T, typename Alloc = CustomAllocator<T>>
class vector
{
public:
vector() : vec(nullptr), size(0), idx(0) {}
void reserve(size_t size)
{
vec = allocator.allocate(size);
this->size = size;
}
//1.
//void push_back(const T &val)
//{
// //构造一个值为val的对象
// allocator.construct(vec + idx, val);
// idx++;
//}
//void push_back(T &&val)
//{
// allocator.construct(vec + idx, std::move(val));
// idx++;
//}
//2.使用模板
template<typename Type>
void push_back(Type&& val)
{
allocator.construct(vec + idx, std::forward<Type>(val));
idx++;
}
//可变参模板
//1.引用折叠 实参:左值Test Types推导为Test& -> Test&+&& = Test&
// 实参:右值 Types推导为Test -> Test&&
template<typename... Types>
void emplace_back(Types&&... args)
{
//无论是左值引用还是右值引用变量,它本身是个左值。
//2.传递的过程中要保持args的引用类型,使用完美转发
allocator.construct(vec + idx, std::forward<Types>(args)...);
idx++;
}
private:
T* vec;
int size;
int idx;
Alloc allocator;
};
可以看到对于emplace_back
因为参数个数不确定,这里用到了变参模板,以及为了保持引用类型,用到的完美转发技术。使用自定义的vector
运行,查看输出可以发现构造部分与本身的vector
相同。
通过这个例子,我们也可以简单的窥视STL库中的vector
实现。