本篇依赖知识:右值引用、引用折叠、完美转发、可变参数模板,可以看下面两篇博客。
《C++ 引用揭秘:左值与右值的“相爱相杀”》_c++ 左值引用 右值引用-CSDN博客
强烈推荐:C++11 - 右值引用_大秦坑王 右值引用+引用折叠原理-CSDN博客
在C++11的STL容器中,新增了emplace_back和emplace插入方法,据说很高效?下面我们来谈谈。
来看下面的代码:
#include<iostream>
#include<vector>
using namespace std;
class Test
{
public:
Test(int a) { cout << "Test(int)" << endl; }
Test(int a, int b) { cout << "Test(int,int)" << endl; }
Test(const Test&) { cout << "const Test&" << endl; }
Test(Test&&) { cout << "Test&&" << endl; }
~Test() { cout << "~Test()" << endl; }
};
int main()
{
Test t1(10);
vector<Test> v;
v.reserve(100);
cout << "-------------" << endl;
// 直接插入对象,两个是没有区别的
// 插入左值
v.push_back(t1);
v.emplace_back(t1);
cout << "-------------" << endl;
// 直接插入对象,两个是没有区别的
// 插入右值
v.push_back(Test(20));
v.emplace_back(Test(20));
cout << "-------------" << endl;
return 0;
}
直接插入对象,push_back和emplac_back是没有区别的。那emplace有什么用呢?高效在哪呢?
#include<vector>
#include<iostream>
using namespace std;
class Test
{
public:
Test(int a) { cout << "Test(int)" << endl; }
Test(int a, int b) { cout << "Test(int,int)" << endl; }
Test(const Test&) { cout << "const Test&" << endl; }
Test(Test&&) { cout << "Test&&" << endl; }
~Test() { cout << "~Test()" << endl; }
};
int main()
{
Test t1(10);
vector<Test> v;
v.reserve(100);
cout << "-------------" << endl;
// 直接插入对象,两个是没有区别的
v.push_back(t1);
v.emplace_back(t1);
cout << "-------------" << endl;
// 直接插入对象,两个是没有区别的
v.push_back(Test(20));
v.emplace_back(Test(20));
cout << "-------------" << endl;
// 直接给emplace传入对象构造所需要的参数,直接在容器底层构造对象即可
v.emplace_back(20);
v.emplace_back(30, 40);
cout << "-------------" << endl;
/*
map<int,string> m;
m.insert(make_pair(20,"zhangsan"));
m.emplace(20,"zhangsan"); // 在map底层直接调用普通构造函数,生成一个pair对象即可
*/
return 0;
}
push_back无论是插入左值还是右值,都需要额外定义或创建对象,并且调用拷贝构造/移动构造。emplace_back插入可以直接传入对象构造所需要的参数,在容器底层直接使用构造函数构造对象。没有创建定义额外的对象,也没有调用拷贝构造/移动构造。减少了资源的开销,提高了插入的效率,并且使用起来更简单。
/*
map<int,string> m;
m.insert(make_pair(20,"zhangsan"));
m.emplace(20,"zhangsan"); // 在map底层直接调用普通构造函数,生成一个pair对象即可
*/
emplace_back是怎么做到的呢?我们来模拟vector的emplace_back看一下。
#include<iostream>
using namespace std;
class Test
{
public:
Test(int a) { cout << "Test(int)" << endl; }
Test(int a, int b) { cout << "Test(int,int)" << endl; }
Test(const Test&) { cout << "const Test&" << endl; }
Test(Test&&) { cout << "Test&&" << endl; }
~Test() { cout << "~Test()" << endl; }
};
// 实现空间配置器
template<typename T>
struct MyAllocator
{
// allocate开辟内存 deallocate释放内存
// construct构造对象 destory析构对象
T* allocate(size_t size)
{
return (T*)malloc(size * sizeof(T));
}
template<typename... Types>
void construct(T* ptr, Types&&... args)
{
new (ptr) T(std::forward<Types>(args)...); // 可以接收多个参数,直接构造
}
// deallocate
// ...
// destory
// ...
};
template<typename T,typename Alloc = MyAllocator<T>>
class vector
{
public:
vector() :vec_(nullptr), size_(0), idx_(0) {}
// 预留空间
void reserve(size_t size)
{
vec_ = allocator_.allocate(size);
size_ = size;
}
// push_back
void push_back(const T& val)
{
allocator_.construct(vec_ + idx_, val);
idx_++;
}
void push_back(T&& val)
{
allocator_.construct(vec_ + idx_, std::move(val));
idx_++;
}
//template<typename Types>
//void push_back(Types&& val)
//{
// allocator_.construct(vec_ + idx_, std::forward<Types>(val));
// idx_++;
//}
// 可变参数模板+引用折叠
template<typename... Types>
void emplace_back(Types&&... args)
{
// 不管参数是左值引用变量还是右值引用变量。它本身是一个左值。
// 传递的过程中要使用std::forward完美转发保持args的原有引用类型属性。
allocator_.construct(vec_ + idx_, std::forward<Types>(args)...);
idx_++;
}
private:
T* vec_;
int size_; // 容量
int idx_; // 元素个数
Alloc allocator_;
};
int main()
{
Test t1(10);
vector<Test> v;
v.reserve(100);
cout << "-------------" << endl;
// 直接插入对象,两个是没有区别的
v.push_back(t1);
v.emplace_back(t1);
cout << "-------------" << endl;
// 直接插入对象,两个是没有区别的
v.push_back(Test(20));
v.emplace_back(Test(20));
cout << "-------------" << endl;
// 可以直接给emplace传入对象构造所需要的参数,在容器底层构造对象即可
v.emplace_back(20);
v.emplace_back(30, 40);
cout << "-------------" << endl;
/*
map<int,string> m;
m.insert(make_pair(20,"zhangsan"));
m.emplace(20,"zhangsan"); // 在map底层直接调用普通构造函数,生成一个pair对象即可
*/
return 0;
}
emplace_back使用可变参数模板和引用折叠实现了可以直接传递对象构造所需要的参数,在容器底层直接构造对象。
emplace_back根据传入的参数,匹配相应的方法。如果传入的是一个对象,引用折叠支持对象去匹配相应的拷贝构造或移动构造,如果传入的是对象参数,就直接调用构造函数构造对象。
// push_back
void push_back (const value_type& val);
void push_back (value_type&& val);
// emplace_back
template <class... Args>
void emplace_back (Args&&... args);
总结:emplace_back函数可以直接在容器底层内存位置使用传传入对象构造所需要的参数构造对象,避免了push_back先创建一个对象再进行拷贝或移动的过程,从而减少了资源开销,提高了插入效率。