在开发的过程中需要遍历一个unordered_map
然后把他的迭代器传给另一个对象:
class A;
class B {
public:
void deal(const std::pair<int, A>& item);
};
std::unordered_map<int, A> mp;
B b;
for (auto &pr : mp) {
b.deal(pr);
}
在我的项目中A类管理了系统资源,我震惊地发现经过deal
函数处理后A类所管理的资源会被释放(调用了析构函数)。这让我百思不得其解,我只是传递了一个引用而已啊,为什么会发生析构呢?经过调试发现在调用deal
函数的时候发生了pair
的拷贝操作,由于A类的拷贝构造是共享资源,所以在deal
函数的参数pr
析构的时候释放了资源。
那么问题就变成了为什么将mp
的元素pr
传递给deal
就会发生拷贝?我们知道C++中普通引用要求两者的类型必须一致,而常量引用却可以发生类型转换,莫非是因为形参和实参的类型不一致?由于range循环的元素pr
是推断出来的,那么的确有可能出现这种情况。接下来我们的问题就是搞清楚auto &pr
是什么类型,然后修改deal
的形参类型即可。(当然也可以将deal
变成模板成员函数,然后用万能引用也可以解决问题)。
《Effective Morden C++》的条款4向我们介绍了如何查看型别推导的结果,有三种方法,分别是依赖IDE、依赖编译器诊断信息和在运行时输出。我使用的是Clion进行开发,还是很靠谱地显示了pr
的类型为:std::pair<const int, A>&
,而我们在类B中声明的形参为const std::pair<int, A>&
,怪不得发生了拷贝构造。 于是我们修改B中的形参就可以啦。
如果IDE没有这么智能,那么还是可以依赖我们的好兄弟编译器的,《Effective Morden C++》中介绍了一种很trick的方法就是使用一个没有定义的模板类或者模板函数,只要我们试图使用这个模板,就会诱发一个错误信息,而编译器肯定会在错误信息中加上类型的名字。
于是我写了一个简单的测试函数:
template<typename T>
void printType(T t);
经过运行,编译器发出了错误警告:
undefined reference to `void edward::printType<std::pair<int const, edward::A> >(std::pair<int const, edward::A>)’
我也尝试了第三种看起来最优雅的运行时输出,书中喷了一下标准库的typeid
的实现,推荐使用boost
库中的boost::typeindex::type_id_with_cvr<T>().pretty_name()
进行输出。但是我可能没有正确安装好boost库,导致找到这个函数。
总之,我们要千万小心map
和unordered_map
的元素类型为std::pair<const key_type, mapped_type>
,如果忘记const
可能会不小心产生拷贝操作,不仅仅会带来性能损耗,而且可能会产生RuntimeError。