假设有这么一个类
class Person1 {
public:
Person1(string name) :pName_(new string(name)) { }
Person1(const Person1& p) :pName_(p.pName_) {
cout << "In copy constructor, pName_ = " << p.pName_ << endl;
}
~Person1() {
cout << "Run in Person " << pName_ << " destructor!" << endl;
delete pName_;
}
void printName() {
cout << "Person1 -> " << *pName_ << endl;
}
public:
string* pName_;
};
那么,利用push_back往一个vector容器中插入该类的对象时,会先创建一个匿名对象,然后调用拷贝构造函数创建vector中的对象,再析构掉匿名对象。为了省去创建匿名对象和析构匿名对象的开销,C++11出了emplace_back方法。它和push_back的区别是:**当插入rvalue,它节约了一次move构造。当插入lvalue,它节约了一次copy构造。**因此,理论上emplace_back不会发生拷贝,但是vector底层发生了内存分配扩张时,还是会进行拷贝构造。见下面例子:
int main() {
vector<Person1> person1s;
cout << person1s.capacity() << endl;
person1s.emplace_back("Hik1"); // 不会创建匿名对象,而是在vector自身的空间中创建对象,因此也不会析构匿名对象
cout << person1s[0].pName_ << endl;
person1s.emplace_back("Hik2");
cout << person1s[0].pName_ << endl;
cout << person1s[1].pName_ << endl;
cout << person1s.capacity() << endl;
return 0;
}
输出:
0 // 一开始时,容量为0
01204F40 // 第一次创建对象,该数字是"Hik1"字符串在内存中的地址
In copy constructor, pName_ = 01204F40 // 第二次创建对象,调用拷贝,从"pName_ = 01204F40"可以看出,是利用第一次的对象"Hik1"来创建了一个匿名对象
Run in Person 01204F40 destructor! // 第二次创建对象完毕,析构掉匿名对象,也即把"Hik1"字符串的空间释放了,此时对象1的pName_已是野指针了
01204F40 // 对象1的pName_,值还在,空间及内容已释放
0120F2A0 // 对象2的pName_值
2 // 容量扩充为2
第一次emplace_back时有没有调用拷贝构造函数呢?乍看上面的打印似乎没有,但是如果做个测试,发现其实还是调用的。第一次emplace_back对象时,容量从0增加到1,也在扩容,而扩容时就会调用。只是第一次扩容时,没有现成的元素对象作为被拷贝,所以没有打印。同样,析构也是一样,没有现成的而已。可以这样测试,直接Person1(const Person1&) = delete;,此时编译器报错“尝试引用已删除的函数”。
更进一步,再emplace_back一个对象试下
int main() {
vector<Person1> person1s;
cout << person1s.capacity() << endl;
person1s.emplace_back("Hik1");
cout << person1s[0].pName_ << endl;
person1s.emplace_back("Hik2");
cout << person1s[0].pName_ << endl;
cout << person1s[1].pName_ << endl;
person1s.emplace_back("Hik3");
cout << person1s.capacity() << endl;
return 0;
}
输出:
0
00A54F40
In copy constructor, pName_ = 00A54F40
Run in Person 00A54F40 destructor!
00A54F40
00A61538
In copy constructor, pName_ = 00A54F40 // 从这里看出,利用对象1和对象2进了两次拷贝构造函数,说明直接扩充了2个空间(虽然只是插入一个元素)
In copy constructor, pName_ = 00A61538 // 所以推断vector容器在扩容时是在当前容量的基础上直接翻倍
Run in Person 00A54F40 destructor! // 这里要析构两个匿名对象,由于对象1在前一次扩容时已经析构了,所以程序运行到这里崩溃
D:\Debug\cpp11_14.exe (进程 11428)已退出,代码为 -1。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
鉴于上面的问题,可以采用在开始时直接预留空间的方式,防止反复扩容,多次调用拷贝构造和析构函数,提升效率。见如下代码:
int main() {
vector<Person1> person1s;
person1s.reserve(10);
cout << person1s.capacity() << endl;
person1s.emplace_back("Hik1");
cout << person1s[0].pName_ << endl;
person1s.emplace_back("Hik2");
cout << person1s[0].pName_ << endl;
cout << person1s[1].pName_ << endl;
person1s.emplace_back("Hik3");
cout << person1s.capacity() << endl;
return 0;
}
输出:
10 // 容量是10
00EA5230
00EA5230
00EAF430
10 // 容量还是10
可以看见,当预留了足够的空间后,没有发生扩容,也没有调用拷贝构造函数和析构函数,程序运行效率得到了提升。
当然,可以采用deque,该容器没有扩容一说,如下:
int main() {
deque<Person1> person1s; // 采用deque
person1s.emplace_back("Hik1");
cout << person1s[0].pName_ << endl;
cout << endl;
person1s.emplace_back("Hik2");
cout << person1s[0].pName_ << endl;
cout << person1s[1].pName_ << endl;
cout << endl;
person1s.emplace_back("Hik3");
cout << person1s[0].pName_ << endl;
cout << person1s[1].pName_ << endl;
cout << person1s[2].pName_ << endl;
cout << endl;
person1s.emplace_back("Hik4");
cout << person1s[0].pName_ << endl;
cout << person1s[1].pName_ << endl;
cout << person1s[2].pName_ << endl;
cout << person1s[3].pName_ << endl;
cout << endl;
person1s.front().printName();
return 0;
}
输出:
011E9760
011E9760
011EF2C0
011E9760
011EF2C0
011EF1E8
011E9760
011EF2C0
011EF1E8
011EF668
Person1 -> Hik1 // 没有进入到拷贝构造函数,也没有析构匿名对象,程序也没有出现崩溃。