std :: map迭代-调试和发布版本之间的顺序差异

时间:2020-03-06 14:45:38  来源:igfitidea点击:

这是我必须使用的常见代码模式:

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(或者类似方法)比较键的键比较函数。这样,地图将根据字符串的内容而不是字符串的指针值(即内存中的位置)进行排序。