问题实例
</br>
假设有一个Widget类:1
2
3
4
5
6
7class Widget {
public:
Widget();
Widget(double weight);
Widget& operator=(double weight);
...
}
我们试图建立起int、widget之间的映射关联,那么使用使用map是再恰当不过的:1
2
3map<int, Widget> m;
m[1] = 1.50;//一一添加映射关系
m[2] = 3.67;
然而这种写法对性能的冲击极大。
map::operator[]与map::insert
map::operator[]的具体实现
map的operator[]
与别的容器的operator[]
颇有不同,它所执行的功能是“更新或添加”,具体来说,1
m[k] = v;
上述表达式所执行的操作依次为:
- 检查键k是否在map中,如果没有,则insert
pair<const k,v>
- 有,则更新k所对应的value
operator[]
默认返回一个value的引用,这在更新操作中再正常不过,但对于insert操作,它会使用value的默认构造函数构造一个,然后返回这个新建立对象的引用。具体来说,1
m[1] = 1.50;//m中不存在k==1
等价于1
2
3typedef map<int, Widget> IntWidgetMap;
pair<IntWidgetMap::iterator, bool> result = m.insert(IntWidgetMap::value_type(1, Widget()));
result.first->second = 1.50;
用insert直接取代operator[]中的插入操作
显然,上述代码在map中插入了一个pair,其first是我们指定的key,second则是默认初始化的widget,最后再执行了赋值操作。
以value初始化widget自然会比上述代码更加高效,所以,我们应该直接使用insert代替operator[]
:1
m.insert(IntWidgetMap::value_type(1, 1.50));
这种写法一共节约了3次函数调用:
- 一次建立widget临时对象
- 一次销毁widget临时对象
- 一次widget赋值操作
两全其美的方法
</br>
出于对更高效的渴望,我们能否扬长避短,实现如下的功能?1
2
3//如果k在m中,调用operator[],否则执行insert
//返回被指向pair的迭代器
iterator affectedPair = efficientAddOrUpdate(m, k, v);
实现该函数实际上并不困难:1
2
3
4
5
6
7
8
9
10
11
12
13template<typename MapType,typename KeyArgType, typename ValueArgtype>
typename MapType::iterator //利用typename强调此处是一个类型而非成员
efficientAddOrUpdate(MapType& m,const KeyArgType& k,const ValueArgtype& v){
typename MapType::iterator Ib = m.lower_bound(k);//找出合理插入位置
if(Ib != m.end() && !(m.key_comp()(k, Ib->first))) {//关联容器判同
Ib->second = v; //更新
return Ib;
}
else{
typedef typename MapType::value_type MVT;
return m.insert(Ib, m::value_type(k, v));//常数时间完成插入
}
}
在上述实例中,keytype与valuetype不必是存储在map里的类型,它们只需要能够完成隐式转换即可。
总结
</br>
本节的重点并不在于如何去实现AddOrUpdate这样的函数,而在于强调operator[]
与insert在不同情况下效率的不同,如果我们能明确程序行为,那我们应当谨慎地选用它们中的一个。