在 C++ 中,为什么要在“const char array”和包装“const char*”的私有结构上重载函数?

2024-03-03

我最近在 ENTT 图书馆遇到了一个有趣的课程。此类用于计算字符串的哈希值,如下所示:

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。下面是该类实现的简化视图来说明这一点:

/*
   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>

这个另一个类提供了一个成员函数,可以使用键等字符串将资源添加到地图中。其实现的简化视图如下所示:

bool addResource(hashed_string id, Resource res)
{
    // ...
    resourceMap[id] = res;
    // ...
}

我的问题是:使用 hashed_strings 作为映射的键而不是 std::strings 有什么优势?使用像 hashed_strings 这样的数字类型是否更有效?

感谢您提供任何信息。学习这门课让我学到了很多东西。


作者试图帮助您避免重复散列字符串时发生的意外性能问题。由于散列字符串的成本很高,因此您可能需要执行一次并将其缓存在某个地方。如果它们具有隐式构造函数,您可以在不知情或不打算这样做的情况下重复散列同一字符串。

所以图书馆提供implicit字符串文字的构造,可以在编译时通过以下方式计算constexpr but explicit建设为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.
}

If I remove the last line, you can see how the compiler applies the implicit conversions for you at :

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!

This case is less common, and such arrays typically get decayed into pointers as they are passed to other functions, which would then behave as above. The library could conceivably enforce compile-time hashing for string literals with if constexpr and the like and forbid it for non-literal arrays like s above. (There's your pull request to give back to the library!) [See comments.]

回答你的最后一个问题:这样做的原因是为了让基于哈希的容器具有更快的性能,例如std::unordered_map。它通过计算一次哈希值并将其缓存在内部,最大限度地减少了必须执行的哈希值数量。hashed_string。现在,映射中的键查找只需比较键和查找字符串的预先计算的哈希值。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 C++ 中,为什么要在“const char array”和包装“const char*”的私有结构上重载函数? 的相关文章

  • 添加 Nullable int 时保持 null?

    我想添加可为空的int 并保留null当所有值都是null 我想要这个结果 1 2 3 1 null 1 null null null O null 0 问题是 如果我将一个值与 null 相加 结果为 null int i1 1 int
  • CMake 找不到请求的 Boost 库

    既然我已经浏览了其他人的解决方案几个小时 但找不到适合我的问题的正确答案 我想将我的具体问题带给您 我正在尝试使用 CMake 构建 vsomeip 为此 我之前构建了 boost 1 55 但是 我在 CMake 中收到以下错误 The
  • Poco c++Net:Http 从响应中获取标头

    我使用 POCO C Net 库进行 http 我想尝试制定持久缓存策略 首先 我认为我需要从缓存标头中获取过期时间 并与缓存值进行交叉检查 如果我错了 请告诉我 那么我如何从中提取缓存头httpResponse 我已经看到你可以用 Jav
  • 单元测试验证失败

    我正在运行我的单元测试PostMyModel路线 然而 在PostMyModel 我用的是线Validate
  • 如何在另一个应用程序中挂钩 api 调用

    我正在尝试挂钩另一个应用程序的 ExtTextOut 和 DrawTextExt GDI 方法调用 我知道我需要使用 GetProcAddress 来查找 gdi32 dll 中那些方法的地址 并用我的函数的地址覆盖我想要挂钩的进程中的地址
  • 检测wlan是否关闭

    任何人都可以给我一个提示 如何在 Windows Phone 上以编程方式检测 C 8 1 应用程序 不是 8 0 是否启用 禁用 WLAN 我不想更改这些设置 只是需要知道 该解决方案是一个 Windows 8 1 通用应用程序 Wind
  • 将完整模板参数值映射到原始类型

    我想将数字映射到类型 在这个例子中 我将创建一个函数 将 sizeof 结果映射到有符号的原始类型 我想知道是否有更好的方法来完成我在现代 C 中所做的事情 即采用模板化值并将其转换为类型 现在 这可以将大小转换为已知类型 但我似乎无法在标
  • Gwan C#,如何获取HTTP标头?

    我需要它来重写 url 以了解我正在处理哪个友好的 url 用于用户代理和其他东西 EDIT public class Gwan MethodImplAttribute MethodImplOptions InternalCall exte
  • 一元 +/- 运算符如何可能导致“-a”或“+a”中的整数提升,“a”是算术数据类型常量/变量?

    这句看似微不足道的台词摘自我的迈克 巴纳汉和布雷迪的 C 书 第 2 8 8 2 节 http publications gbdirect co uk c book chapter2 expressions and arithmetic h
  • 将表(行)与 OpenXML SDK 2.5 保持在一起

    我想在 Word 文档中生成多个表 每行 2 行 但我想将这两行保留在一起 如果可能的话 new KeepNext 第一行不起作用 new KeepNext 第一行的最后一段不起作用 new CantSplit 放在桌子上不起作用 在所有情
  • 增强精神、递归和堆栈溢出

    为什么下面的代码在运行时崩溃 它会给出堆栈溢出错误 include
  • C#6 中的长字符串插值行

    我发现 虽然字符串插值在应用于现有代码库的字符串 Format 调用时非常好 但考虑到通常首选的列限制 字符串对于单行来说很快就会变得太长 特别是当被插值的表达式很复杂时 使用格式字符串 您将获得一个可以拆分为多行的变量列表 var str
  • 搜索实体的所有字段

    我正在尝试在客户数据库上实现 多功能框 类型的搜索 其中单个查询应尝试匹配客户的任何属性 这是一些示例数据来说明我想要实现的目标 FirstName LastName PhoneNumber ZipCode Mary Jane 12345
  • 如何使用 NPOI 按地址(A1、A2)获取 Excel 单元格值

    我有一个 Excel 单元格地址 例如 A1 A2 如何使用 C 中的 NPOI 框架以编程方式访问此单元格 我找到的一些 Java POI 示例代码 CellReference cr new CellReference A1 row my
  • Project Euler #8,我不明白我哪里出了问题[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我正在做项目欧拉第八题 https projecteuler net problem 8 其中我得到了这个大得离谱的数字 7316
  • 使用 GCC 生成可读的程序集?

    我想知道如何使用GCC http en wikipedia org wiki GNU Compiler Collection在我的 C 源文件中转储机器代码的助记符版本 这样我就可以看到我的代码被编译成什么 你可以使用 Java 来做到这一
  • ASP.NET MVC 路由:如何从 URL 中省略“索引”

    我有一个名为 StuffController 的控制器 具有无参数索引操作 我希望从表单中的 URL 调用此操作mysite com stuff 我的控制器定义为 public class StuffController BaseContr
  • 如何在 winforms 应用程序的主屏幕显示之前显示欢迎屏幕?

    我想在应用程序启动时加载欢迎屏幕 然后用户单击欢迎屏幕上的按钮 然后关闭欢迎屏幕 最后显示主屏幕 static void Main startup method being called Application EnableVisualSt
  • 通过 Tab 键浏览 XML 文档字段

    In VB NET you can move through the fields in the XML member documentation with the Tab key 这在 C 中不起作用 还有其他方法吗 除了用鼠标将光标放在
  • 如何将 SQL“LIKE”与 LINQ to Entities 结合使用?

    我有一个文本框 允许用户指定搜索字符串 包括通配符 例如 Joh Johnson mit ack on 在使用 LINQ to Entities 之前 我有一个存储过程 该存储过程将该字符串作为参数并执行以下操作 SELECT FROM T

随机推荐