最近在做一些服务性能优化方面的工作,其中大量使用到map和unordered_map,于是花了些时间对STL的这两个容器调研了下。
本文章说的诡异行为在map和unordered_map都存在,所以在代码中只使用map来说明问题。
话不多说,直接上代码:
#include <iostream>
#include <map>
#include <utility>
using namespace std;
class Sample
{
public:
Sample()
{
std::cout << "Inside default constructor of object. " << this << std::endl;
}
Sample(const Sample& sample)
{
std::cout << "Inside copy constructor of object. " << &sample << " to " << this << std::endl;
}
Sample& operator= (const Sample& sample)
{
std::cout << "Inside copy operator= of object. " << &sample << " to " << this << std::endl;
return *this;
}
//Sample(Sample&& sample)
//{
// std::cout << "Inside move constructor of object. " << &sample << " to " << this << std::endl;
//}
//Sample& operator= (Sample&& sample)
//{
// std::cout << "Inside move operator= of object. " << &sample << " to " << this << std::endl;
// return *this;
//}
~Sample()
{
std::cout << "Destroying object. " << this << std::endl;
}
};
int main(int argc, char* argv[])
{
std::map<int, Sample> map;
std::cout << "----------------------------" << std::endl;
Sample sample;
std::cout << "----------------------------" << std::endl;
//map.insert(std::make_pair(1, sample));
Sample& obj = map[1];
std::cout << "----------------------------" << std::endl;
obj = sample;
std::cout << "----------------------------" << std::endl;
return 0;
}
本文采用g++ 4.8.5作为编译器。
首先,使用没有C++11编译选项来编译:
g++ -Wall maptest.cpp -o maptest
运行结果:
----------------------------
Inside default constructor of object. 0x7ffcc71faad3
----------------------------
Inside default constructor of object. 0x7ffcc71faa5f
Inside copy constructor of object. 0x7ffcc71faa5f to 0x7ffcc71faa54
Inside copy constructor of object. 0x7ffcc71faa54 to 0x16e6034
Destroying object. 0x7ffcc71faa54
Destroying object. 0x7ffcc71faa5f
----------------------------
Inside copy operator= of object. 0x7ffcc71faad3 to 0x16e6034
----------------------------
Destroying object. 0x7ffcc71faad3
Destroying object. 0x16e6034
从上面的结果可以看到,前面两个默认构造应该很容易理解,后面的拷贝赋值和两次析构也很容易理解。
唯一无法理解的是,中间的两次拷贝构造和两次析构。
很显然,这无法理解的两次拷贝构造和两次析构是从map[1]中来的。
在map[1]时,①默认构造 0x7ffcc71faa5f;②拷贝构造时,将 0x7ffcc71faa5f 赋值给新对象 0x7ffcc71faa54;③再拷贝构造时,将 0x7ffcc71faa54 赋值给新对象 0x16e6034;④最后析构 0x7ffcc71faa54 和 0x7ffcc71faa5f,留下最后的新对象 0x16e6034。
然后,使用带有C++11编译选项来编译:
g++ -Wall --std=c++11 maptest.cpp -o maptest
运行结果:
----------------------------
Inside default constructor of object. 0x7fffdd37b533
----------------------------
Inside default constructor of object. 0x207b034
----------------------------
Inside copy operator= of object. 0x7fffdd37b533 to 0x207b034
----------------------------
Destroying object. 0x7fffdd37b533
Destroying object. 0x207b034
从上面的结果可以看到,运行结果是符合预期的。
最后,又对比了map的insert和[]的性能。
int main(int argc, char* argv[])
{
std::map<int, Sample> map;
std::cout << "----------------------------" << std::endl;
Sample sample;
std::cout << "----------------------------" << std::endl;
map.insert(std::make_pair(1, sample));
//Sample& obj = map[1];
//std::cout << "----------------------------" << std::endl;
//obj = sample;
std::cout << "----------------------------" << std::endl;
return 0;
}
同样使用带有C++11编译选项来编译,运行结果:
----------------------------
Inside default constructor of object. 0x7fffc7e34b5f
----------------------------
Inside copy constructor of object. 0x7fffc7e34b5f to 0x7fffc7e34b64
Inside copy constructor of object. 0x7fffc7e34b64 to 0x1a52034
Destroying object. 0x7fffc7e34b64
----------------------------
Destroying object. 0x7fffc7e34b5f
Destroying object. 0x1a52034
对比[]的运行结果,可以看到,insert比[]少了一次默认构造和一次拷贝赋值,但是多了两次拷贝构造和一次析构。
[]的运行原理:先向map容器里存入一个默认对象,再将容器外的对象拷贝赋值给容器里的这个对象。
insert的运行原理:先用待存入的对象构造一个中间的std::pair对象,在用这个中间的std::pair对象拷贝构造一个存入在map里的终极std::pair对象。
总结:
在使用容器的时候,一定要使用支持C++11的编译器,并且带上--std=c++11编译选项。并且map的[]性能比insert性能更好。