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
Параметр шаблона см. в документации.