看了很多教程发现大家的说法不太一致,贴一下我自己在vscode里gcc编译的代码,把想看的场景排列组合了,有一些罗嗦,
结论:
- 对于传入的左值对象(或临时右值对象等已经构造好的对象),emplace和push或者insert的行为是一样的,都是一次拷贝构造(或移动构造)
- emplace可以接受用于构造对象的参数,而push和insert必须显式传入已经构造好的对象(临时右值或左值对象),因此在这种场景下避免了部分临时对象的开销
- emplace依赖带参的构造函数,如果没有相应构造函数,直接构造的作用就失效了
- emplace的优势在少量(或者直接传入构造好的对象)元素的传入中并不明显,应用场景是大量复杂数据(而不是对象)
- 对于插入操作,先根据传入对象完成临时对象的拷贝(或移动),再从末开始依次向后移动(构造)尾端元素,再将临时对象插入到实际位置
用于测试的类:Student
class Student {
public:
Student() { cout << " 默认构造函数 " << endl; }
Student(const string& name, int age):_name(name), _age(age) { cout << " 构造函数:" << _name << endl; }
Student(const Student& stu):_name(stu._name), _age(stu._age) { cout << " 拷贝构造:" << _name << endl; }
Student(Student &&stu) : _name(move(stu._name)), _age(move(stu._age)) { cout << " 移动构造函数:" << _name << endl; }
Student& operator=(const Student& stu){
_name = stu._name;
_age = stu._age;
cout << " 拷贝构造函数:" << _name << endl;
return *this;
}
Student& operator=(Student&& stu){
_name = move(stu._name);
_age = move(stu._age);
cout << " 移动构造函数:" << _name << endl;
return *this;
}
private:
string _name;
int _age;
};
具体的测试及输出:
int main() {
Student tom("tom", 25); //构造函数tom
Student tom1("tom1", 25); //构造函数tom1
Student tom2("tom2", 25); //构造函数tom2
Student tom3("tom3", 25); //构造函数tom3
vector<Student> v1;
v1.reserve(20);
v1.push_back(tom); //拷贝构造函数tom
v1.emplace_back(tom); //拷贝构造函数tom
v1.push_back(move(tom)); //移动构造函数tom
Student tom4("tom4", 25); //构造函数tom4
v1.emplace_back(move(tom4)); //移动构造函数tom4
v1.insert(v1.begin() + v1.size(), tom1); //向尾部插入,无需要移动的元素,直接拷贝构造tom1
v1.emplace(v1.begin() + v1.size(), tom1); //向尾部插入,无需要移动的元素,直接拷贝构造tom1
v1.insert(v1.begin(), tom2); //先拷贝tom2构造临时对象,再移动(构造)尾端元素,再插入位置;包括tom3在内的size()次移动构造
v1.emplace(v1.begin(), tom3); //先拷贝tom3构造临时对象,再依次移动(构造)尾端元素,再插入位置;包括tom3在内的size()次移动构造
v1.push_back(Student("tom5", 26)); //构造函数(tom5的临时对象);移动构造函数(tom5)
v1.emplace_back("tom6", 27); //构造函数tom6
v1.insert(v1.begin() + v1.size(), Student("tom7", 28)); //构造函数(tom7的临时对象);移动构造函数(tom7)
v1.emplace(v1.begin() + v1.size(), "tom8", 29); //构造函数;
v1.insert(v1.begin() + v1.size()-1, Student("tom9", 28)); //构造函数;移动构造函数(尾端元素 + tom9)
v1.emplace(v1.begin() + v1.size() - 1, "tom10", 29); //构造函数;移动构造函数(尾端元素 + tom10)
return 0;
}
下面是根据cppreference给出的官方例子做的实验:
#include <vector>
#include <string>
#include <iostream>
struct President
{
std::string name;
std::string country;
int year;
President(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";
}
President(President&& other)
: name(std::move(other.name)), country(std::move(other.country)), year(other.year)
{
std::cout << "I am being moved.\n";
}
President& operator=(const President& other) = default;
};
int main()
{
std::vector<President> elections;
std::cout << "emplace_back:\n";
elections.emplace_back("Nelson Mandela", "South Africa", 1994); //可以接受一组参数用于初始化类对象
std::vector<President> reElections;
std::cout << "\npush_back:\n";
reElections.push_back(President("Franklin Delano Roosevelt", "the USA", 1936)); //必须显式传入类对象,需要临时对象
std::cout << "\nContents:\n";
for (President const& president: elections) {
std::cout << president.name << " was elected president of "
<< president.country << " in " << president.year << ".\n";
}
for (President const& president: reElections) {
std::cout << president.name << " was re-elected president of "
<< president.country << " in " << president.year << ".\n";
}
}
输出为:
emplace_back:
I am being constructed.
push_back:
I am being constructed.
I am being moved.
Contents:
Nelson Mandela was elected president of South Africa in 1994.
Franklin Delano Roosevelt was re-elected president of the USA in 1936.
可以看到,push_back调用了两个构造函数
再多添加几个元素:
#include <vector>
#include <string>
#include <iostream>
struct President
{
std::string name;
std::string country;
int year;
President(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";
}
President(President&& other)
: name(std::move(other.name)), country(std::move(other.country)), year(other.year)
{
std::cout << "I am being moved.\n";
}
President& operator=(const President& other) = default;
};
int main()
{
std::vector<President> elections;
std::cout << "emplace_back:\n";
elections.emplace_back("Nelson Mandela", "South Africa", 1994);
std::cout << std::endl;
elections.emplace_back("Nelson Mandela", "South Africa", 1994);
std::cout << std::endl;
elections.emplace_back("Nelson Mandela", "South Africa", 1994);
std::cout << std::endl;
for (President const& president: elections) {
std::cout << president.name << " was re-elected president of "
<< president.country << " in " << president.year << ".\n";
}
}
输出为:
emplace_back:
I am being constructed.
I am being constructed.
I am being moved.
I am being constructed.
I am being moved.
I am being moved.
Nelson Mandela was re-elected president of South Africa in 1994.
Nelson Mandela was re-elected president of South Africa in 1994.
Nelson Mandela was re-elected president of South Africa in 1994.
为什么也调用了多次拷贝构造呢?因为和原本vector中剩下元素的数量对应,猜测因为发生了整体的vector内存搬移
先进行内存reverse,再进行测试看看
#include <vector>
#include <string>
#include <iostream>
struct President
{
std::string name;
std::string country;
int year;
President(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";
}
President(President&& other)
: name(std::move(other.name)), country(std::move(other.country)), year(other.year)
{
std::cout << "I am being moved.\n";
}
President& operator=(const President& other) = default;
};
int main()
{
std::vector<President> elections;
elections.reserve(3);
std::cout << "emplace_back:\n";
elections.emplace_back("Nelson Mandela", "South Africa", 1994);
std::cout << std::endl;
elections.emplace_back("Nelson Mandela", "South Africa", 1994);
std::cout << std::endl;
elections.emplace_back("Nelson Mandela", "South Africa", 1994);
std::cout << std::endl;
for (President const& president: elections) {
std::cout << president.name << " was re-elected president of "
<< president.country << " in " << president.year << ".\n";
}
}
则可看到输出只调用了普通构造函数
emplace_back:
I am being constructed.
I am being constructed.
I am being constructed.
Nelson Mandela was re-elected president of South Africa in 1994.
Nelson Mandela was re-elected president of South Africa in 1994.
Nelson Mandela was re-elected president of South Africa in 1994.
结论:
- 尽量使用reserve先请求好够用的空间,对性能的提升很重要
- cppreference给出的代码都很有代表性