std :: map迭代-调试和发布版本之间的顺序差异
这是我必须使用的常见代码模式:
class foo { public: void InitMap(); void InvokeMethodsInMap(); static void abcMethod(); static void defMethod(); private: typedef std::map<const char*, pMethod> TMyMap; TMyMap m_MyMap; } void foo::InitMap() { m_MyMap["abc"] = &foo::abcMethod; m_MyMap["def"] = &foo::defMethod; } void foo::InvokeMethodsInMap() { for (TMyMap::const_iterator it = m_MyMap.begin(); it != m_MyMap.end(); it++) { (*it->second)(it->first); } }
但是,我发现根据构建配置是Release还是Debug,处理映射(在for循环内)的顺序可能会有所不同。似乎在Release版本中进行的编译器优化会影响此顺序。
我以为通过在上面的循环中使用begin()
,并在每次方法调用之后增加迭代器,它将按初始化顺序处理映射。但是,我还记得阅读过将映射实现为哈希表的情况,并且不能保证顺序。
这尤其令人烦恼,因为大多数单元测试都是在Debug版本上运行的,并且通常在外部QA团队开始测试之前才发现奇怪的订单依赖性错误(因为它们使用Release版本)。
谁能解释这种奇怪的行为?
解决方案
不要使用const char *作为映射的键。这意味着映射是根据字符串的地址而不是字符串的内容排序的。使用std :: string
作为密钥类型。
std :: map
不是哈希表,通常实现为一棵红黑树,并且保证按某些条件对元素进行排序(默认情况下,键之间的<<比较)。
map的定义是:
map <键,数据,比较,分配>
最后两个模板参数也是默认值:
比较:较少<键>
Alloc:分配器<值类型>
在地图中插入新值时。将新值(valueToInsert)与旧值按顺序比较(N.B.这不是顺序搜索,标准保证最大插入复杂度为O(log(N))),直到Compare(value,ValueToInsert)返回true为止。因为我们使用的是'const char *'作为键。 Compare Object使用less <const char *>此类仅对两个值进行<运算。因此,实际上我们是在比较指针值(不是字符串),因此顺序是随机的(因为我们不知道编译器将字符串放在何处。
有两种可能的解决方案:
- 更改键的类型,以便它比较字符串值。
- 定义另一个比较类型,它可以满足需求。
我个人(像克里斯一样)只使用std :: string,因为在字符串上使用<运算符会返回基于字符串内容的比较。但是为了论证,我们只能定义一个Compare类型。
struct StringLess { bool operator()(const char* const& left,const char* const& right) const { return strcmp(left,right) < 0; } }; /// typedef std::map<const char*, int,StringLess> TMyMap;
如果要使用const char *作为地图的键,则还要设置一个使用strcmp(或者类似方法)比较键的键比较函数。这样,地图将根据字符串的内容而不是字符串的指针值(即内存中的位置)进行排序。