std::string, приведенная к const char *, не может быть найдена в std::unordered_set<const char *>

Работая над проектом, я столкнулся со следующей проблемой, которую не мог объяснить себе.

У меня есть следующая функция is_in_set(..), которая просто проверяет, находится ли cstring в unordered_set cstrings:

bool is_in_set(const char * str, std::unordered_set<const char *> the_set)
{
    if ( the_set.find( str ) != the_set.end() )
        return true;
    else
        return false;
}

И затем я создал следующий пример основного метода, чтобы продемонстрировать мою проблему:

int main()
{
    std::unordered_set<const char *> the_set({"one",
        "two", "three", "four", "five"});

    std::string str = "three";
    const char * cstr = "three";

    std::cout << "str in set? "
        << is_in_set( str.c_str() , the_set ) << std::endl
        << "cstr in set? " 
        << is_in_set( cstr, the_set ) << std::endl;

    const char * str_conv = str.c_str();

    std::cout << "str_conv in set? "
        << is_in_set( str_conv , the_set ) << std::endl
        << "strcmp(str_conv, cstr) = " << strcmp( str_conv , cstr )
        << std::endl;

    return 0;
}

Я ожидал, что приведенный выше код найдет std:: string, приведенную к const char*, а также cstring в наборе. Вместо этого он генерирует следующий вывод (Visual Studio Community 2017):

str in set? 0
cstr in set? 1
str_conv in set? 0
strcmp(str_conv, cstr) = 0

Я также запустил цикл for для обеих переменных, выводя побайтно (в шестнадцатеричном представлении) для каждой, что приводит к следующему:

74 68 72 65 65 00 = c_str
74 68 72 65 65 00 = str_conv

Почему std:: string, приведенная к const char*, не найдена в наборе? Разве strcmp не должен возвращать значение, отличное от 0 в этом случае?

3 ответа

Решение

За const char *нет перегрузки == оператор, который сравнивает строки по значению, поэтому я считаю, unordered_set Контейнер всегда будет сравнивать указатели, а не значения указанных строк.

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

Использование std::unordered_set<std::string> или предоставьте пользовательский хеш, если вы уверены, что ваши строки не покинут стек, пока вы используете хеш-таблицу, например, статические переменные или размещены с помощью new/malloc и т. д.

Что-то вроде:

struct str_eq {
  bool opeator()(const char* lsh, const char rhs) const noexcept
  {
    return lsh == rhs || 0 == std::strcmp(lsh, rhs);
  }  
};


struct str_hash {
   std::size_t opeator()(const char* str) const noexcept
   {
     // some mur-mur2, google cityhash hash_bytes etc instead of this
      return std::hash<std::string>( std::string(str) ) ();
   }
};

typedef std::unordered_set<const char*, str_hash, str_eq, std::allocator<const char*> > my_string_hashset;

Как отметил @Daniel Pryden, вы проводите сравнение адресов. Чтобы это исправить, вам понадобится либо unordered_set хранить std::string объекты или создать собственное сравнение для unordered_set использовать.

Основываясь на ответе на связанный вопрос, что-то вроде этого:

struct StringEqual
{
    bool operator()(const char* a, const char* b) { return 0 == strcmp(a,b); }
};

std::unordered_set<const char *, std::Hash<const char*>, StringEqual> the_set(
    {"one", "two", "three", "four", "five"});

должен сделать свое дело. Это дает unordered_set лучший оператор для тестирования строк.

Для получения дополнительной информации о Pred Параметр шаблона см. в документации.

Другие вопросы по тегам