std :: map итерация - порядок различий между сборками Debug и Release - PullRequest
4 голосов
/ 26 сентября 2008

Вот общий шаблон кода, с которым мне нужно работать:

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);
    }
}

Однако я обнаружил, что порядок , в котором обрабатывается карта (в цикле for), может отличаться в зависимости от того, является ли конфигурация сборки Release или Debug. Похоже, что оптимизация компилятора, которая происходит в сборках Release, влияет на этот порядок.

Я думал, что используя begin() в цикле выше и увеличивая итератор после каждого вызова метода, он будет обрабатывать карту в порядке инициализации. Однако я также помню, что читал, что карта реализована в виде хеш-таблицы, и порядок не может быть гарантирован.

Это особенно раздражает, так как большинство модульных тестов выполняются в сборке Debug, и часто странные ошибки зависимости порядка не обнаруживаются, пока внешняя команда QA не начнет тестирование (потому что они используют сборку Release).

Кто-нибудь может объяснить это странное поведение?

Ответы [ 3 ]

16 голосов
/ 26 сентября 2008

Не используйте const char* в качестве ключа для карт. Это означает, что карта упорядочена по адресам строк, а не по содержимому строк. Вместо этого используйте std::string в качестве типа ключа.

std::map не является хеш-таблицей, обычно она реализована в виде красно-черного дерева, и элементы гарантированно упорядочены по некоторым критериям (по умолчанию < сравнение между ключами).

10 голосов
/ 26 сентября 2008

Определение карты:
карта

Где последние два параметра шаблона по умолчанию тоже:
Сравните: меньше
Alloc: allocator

При вставке новых значений в карту. Новое значение (valueToInsert) сравнивается со старыми значениями по порядку ( NB . Это не последовательный поиск, стандарт гарантирует максимальную сложность вставки O (log (N))) до Compare (значение, ValueToInsert ) возвращает истину. Потому что вы используете 'const char *' в качестве ключа. Объект сравнения использует меньше , этот класс просто делает <на двух значениях. Таким образом, в действительности вы сравниваете значения указателя (не строки), поэтому порядок является случайным (поскольку вы не знаете, куда компилятор будет помещать строки. </p>

Существует два возможных решения:

  • Измените тип ключа, чтобы он сравнивал строковые значения.
  • Определите другой тип сравнения, который делает то, что вам нужно.

Лично я (как и Крис) просто использовал бы std :: string, потому что оператор <, используемый для строк, возвращает сравнение на основе содержимого строки. Но ради аргументов мы можем просто определить тип сравнения. </p>

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;
3 голосов
/ 26 сентября 2008

Если вы хотите использовать const char * в качестве ключа для вашей карты, также установите функцию сравнения ключей, которая использует strcmp (или аналогичный) для сравнения ключей. Таким образом, ваша карта будет упорядочена по содержимому строки, а не по значению указателя строки (то есть расположению в памяти).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...