在std::unordered_map中使用QString作为键

18

我试图将 QString 作为 std::unordered_map 中的键,但是我收到了以下错误:

error C2280: 'std::hash<_Kty>::hash(const std::hash<_Kty> &)': 尝试引用已删除的函数

我无法切换到 QHash,因为映射的值类型是不可复制的。有没有办法让这个工作?

3个回答

26
将{{hash}}实现放在头文件中,并确保在使用地图的任何地方都包含该头文件。
一个简单的实现方法是转发到{{qHash}}。
#include <QHash>
#include <QString>
#include <functional>

namespace std {
  template<> struct hash<QString> {
    std::size_t operator()(const QString& s) const noexcept {
      return (size_t) qHash(s);
    }
  };
}

尽管在常见的64位平台上,std::size_tunsigned int更大,因此哈希值在其整个长度范围内不会改变,但这并不是问题。标准对std::hash实现没有这样的要求。

不过要记住,修改std命名空间中的任何内容通常是未定义行为且不必要的。

简而言之:
您可以特化某些类型和变量模板,但仅当特化取决于至少一个用户定义的类型时才可以。您无法完全为内置类型或C++标准库类型进行特化。

有关详细信息,请参见扩展命名空间std。在那里,我们可以看到:

向std命名空间或嵌套在std中的任何命名空间添加声明或定义都是未定义行为,但有一些例外情况,如下所示。

主要的例外是允许特化std命名空间中的某些类型:

只有在声明依赖于至少一个程序定义的类型,并且特化满足原始模板的所有要求(除非禁止这种特定),才允许为任何标准库类模板添加模板特化到命名空间std中。

请注意,合法的是对类型进行特化,即类。函数和成员函数呢?从不:

声明任何标准库函数模板的完全特化[...或]标准库类模板的成员函数[...或]标准库类或类模板的成员函数模板是未定义行为。

另一个有限的例外是变量模板:

声明任何标准库变量模板的完全或部分特化,除非明确允许,都是未定义行为。

无论如何,始终有需要了解的进一步细节


8

问题在于没有std::hash<QString>()专门化。根据dbj2算法,很容易定义自己的哈希函数以获得合理的性能:

#include <QString>
#include <unordered_map>

namespace std
{
    template<> struct hash<QString>
    {
        std::size_t operator()(const QString& s) const noexcept
        {
            const QChar* str = s.data();
            std::size_t hash = 5381;

            for (int i = 0; i < s.size(); ++i)
                hash = ((hash << 5) + hash) + ((str->row() << 8) | (str++)->cell());

            return hash;
        }
    };
}

将此内容包含在使用std::unordered_map中的QString的文件中,就可以消除错误。


1
你可以简化为 return std::hash<std::string>()(s.toStdString()); - R Sahu
6
可以,但由于需要将Unicode转换成UTF8并进行内存分配,因此性能不会很好。 - Nicolas Holthaus
6
可能甚至需要返回 qHash(s) - SteakOverflow
这个答案是不正确的,因为 QString 可以包含内部 \0 字符。 - krojew
1
如前所述,只需返回 qHash 值即可更简单。 - krojew
显示剩余2条评论

2

在较新版本的Qt中,似乎已经为QString定义了std::hash,因此您可以直接将其与std::unordered_map一起使用。(在我的机器上,它适用于Qt 5.14。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接