gcc中诡异的map/unordered_map行为:在非C++11编译下,往map中存入对象,会多出两次拷贝构造。

最近在做一些服务性能优化方面的工作,其中大量使用到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性能更好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值