我最近在 ENTT 图书馆遇到了一个有趣的类(class)。此类用于计算字符串的哈希值,如下所示:
std::uint32_t hashVal = hashed_string::to_value("ABC");
hashed_string hs{"ABC"};
std::uint32_t hashVal2 = hs.value();
在查看此类的实现时,我注意到没有任何构造函数或 hashed_string::to_value
成员函数采用 const char*
直接地。相反,他们采用一个名为 const_wrapper
的简单结构。 。下面是该类实现的简化 View 来说明这一点:
/*
A hashed string is a compile-time tool that allows users to use
human-readable identifers in the codebase while using their numeric
counterparts at runtime
*/
class hashed_string
{
private:
struct const_wrapper
{
// non-explicit constructor on purpose
constexpr const_wrapper(const char *curr) noexcept: str{curr} {}
const char *str;
};
inline static constexpr std::uint32_t calculateHash(const char* curr) noexcept
{
// ...
}
public:
/*
Returns directly the numeric representation of a string.
Forcing template resolution avoids implicit conversions. An
human-readable identifier can be anything but a plain, old bunch of
characters.
Example of use:
const auto value = hashed_string::to_value("my.png");
*/
template<std::size_t N>
inline static constexpr std::uint32_t to_value(const char (&str)[N]) noexcept
{
return calculateHash(str);
}
/*
Returns directly the numeric representation of a string.
wrapper parameter helps achieving the purpose by relying on overloading.
*/
inline static std::uint32_t to_value(const_wrapper wrapper) noexcept
{
return calculateHash(wrapper.str);
}
/*
Constructs a hashed string from an array of const chars.
Forcing template resolution avoids implicit conversions. An
human-readable identifier can be anything but a plain, old bunch of
characters.
Example of use:
hashed_string hs{"my.png"};
*/
template<std::size_t N>
constexpr hashed_string(const char (&curr)[N]) noexcept
: str{curr}, hash{calculateHash(curr)}
{}
/*
Explicit constructor on purpose to avoid constructing a hashed
string directly from a `const char *`.
wrapper parameter helps achieving the purpose by relying on overloading.
*/
explicit constexpr hashed_string(const_wrapper wrapper) noexcept
: str{wrapper.str}, hash{calculateHash(wrapper.str)}
{}
//...
private:
const char *str;
std::uint32_t hash;
};
不幸的是,我看不到 const_wrapper
的目的结构。它与顶部的注释有关吗,该注释指出“哈希字符串是编译时工具......”?
我也不确定模板函数上方出现的注释的含义,其中指出“强制模板解析避免隐式转换”。有谁能解释一下吗?
最后,值得注意的是另一个维护 std::unordered_map
的类如何使用该类。以下类型:std::unordered_map<hashed_string, Resource>
这个其他类提供了一个成员函数,可以使用键等字符串将资源添加到 map 中。其实现的简化 View 如下所示:
bool addResource(hashed_string id, Resource res)
{
// ...
resourceMap[id] = res;
// ...
}
我的问题是:使用 hashed_strings 作为映射的键而不是 std::strings 有什么优势?使用像 hashed_strings 这样的数字类型是否更有效?
感谢您提供任何信息。学习这门课让我学到了很多东西。
最佳答案
作者试图帮助您避免重复散列字符串时发生的意外性能问题。由于散列字符串的成本很高,因此您可能需要执行一次并将其缓存在某个地方。如果它们有隐式构造函数,您可以在不知道或不打算这样做的情况下重复散列相同的字符串。
因此,该库为字符串文字提供了隐式构造,可以通过constexpr
在编译时计算。但 const char*
的显式构造一般来说,因为这些通常不能在编译时完成,并且您希望避免重复或意外地这样做。
考虑:
void consume( hashed_string );
int main()
{
const char* const s = "abc";
const auto hs1 = hashed_string{"my.png"}; // Ok - explicit, compile-time hashing
const auto hs2 = hashed_string{s}; // Ok - explicit, runtime hashing
consume( hs1 ); // Ok - cached value - no hashing required
consume( hs2 ); // Ok - cached value - no hashing required
consume( "my.png" ); // Ok - implicit, compile-time hashing
consume( s ); // Error! Implicit, runtime hashing disallowed!
// Potential hidden inefficiency, so library disallows it.
}
如果我删除最后一行,您可以在 C++ Insights 中看到编译器如何为您应用隐式转换。 :
consume(hashed_string(hs1));
consume(hashed_string(hs2));
consume(hashed_string("my.png"));
但它拒绝对 consume(s)
行这样做因为隐式/显式构造函数。
但请注意,这种保护用户的尝试并非万无一失。如果将字符串声明为数组而不是指针,则可能会意外地重新散列:
const char s[100] = "abc";
consume( s ); // Compiles BUT it's doing implicit, runtime hashing. Doh.
// Decay 's' back to a pointer, and the library's guardrails return
const auto consume_decayed = []( const char* str ) { consume( str ); }
consume_decayed( s ); // Error! Implicit, runtime hashing disallowed!
这种情况不太常见,并且此类数组在传递给其他函数时通常会退化为指针,然后这些函数的行为将如上。 可以想象,该库可以对带有 [查看评论。]if constexpr
的字符串文字强制执行编译时哈希。等等,并禁止它用于非文字数组,如 s
多于。 (这是您要回馈给图书馆的拉取请求!)
回答你的最后一个问题:这样做的原因是为了让基于哈希的容器(如 std::unordered_map
)获得更快的性能。 。它通过计算一次哈希值并将其缓存在 hashed_string
中,最大限度地减少了必须执行的哈希值数量。 。现在,映射中的键查找只需比较键和查找字符串的预先计算的哈希值。
关于c++ - 在 C++ 中,为什么要重载 `const char array` 上的函数和包装 `const char*` 的私有(private)结构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56446068/