【发布时间】:2018-07-09 14:46:34
【问题描述】:
我正在尝试使用QString 作为std::unordered_map 中的键,但是我得到了错误:
错误 C2280:'std::hash<_kty>::hash(const std::hash<_kty> &)':试图引用已删除的函数
我无法切换到QHash,因为地图的值类型是不可复制的。有什么办法可以做到吗?
【问题讨论】:
我正在尝试使用QString 作为std::unordered_map 中的键,但是我得到了错误:
错误 C2280:'std::hash<_kty>::hash(const std::hash<_kty> &)':试图引用已删除的函数
我无法切换到QHash,因为地图的值类型是不可复制的。有什么办法可以做到吗?
【问题讨论】:
将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);
}
};
}
即使std::size_t 在常见的 64 位平台上大于 unsigned int,因此散列不会在其全长上发生变化 - 这不是问题。该标准对 std::hash 实现没有这样的要求。
不过,我们不要忘记,修改 std 命名空间中的任何内容通常是未定义的行为且没有必要。
TL;DR:
您可以特化某些 type 和 variable 模板,并且仅当特化依赖于至少一种用户定义的类型时。您不能完全专注于内置或 C++ 库类型。
请参阅Extending the namespace std 以供参考。在那里,我们读到:
将声明或定义添加到命名空间 std 或任何嵌套在 std 中的命名空间是未定义的行为,下面会提到一些例外情况。
主要是允许特化std命名空间中的某些类型:
只有当声明依赖于至少一个程序定义类型和特化时,才允许将任何标准库类模板的模板特化添加到命名空间std满足原始模板的所有要求,除非此类专业化被禁止。
请注意,合法的是专门化类型,即类。函数和成员函数?从来没有:
声明任何标准库函数模板、[...或]标准库类模板的成员函数、[...或]标准库类或类的成员函数模板的完全特化是未定义的行为模板。
另一个有限的例外是变量模板:
声明任何标准库变量模板的全部或部分特化都是未定义的行为,除非明确允许。
在所有情况下都强调我的。与往常一样,还有更多details that one should get acquainted with。
【讨论】:
问题是没有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 的文件中包含它,错误就会消失。
【讨论】:
return std::hash<std::string>()(s.toStdString());
return qHash(s)。
似乎在 Qt 的较新版本中,为 QString 定义了 std::hash,因此您可以直接将其与 std::unordered_map 一起使用。 (它适用于我机器上的 Qt 5.14。)
【讨论】: