В C ++, зачем перегружать функцию в `const char array` и закрытой структуре, оборачивающей` const char * `? - PullRequest
2 голосов
/ 04 июня 2019

Я недавно столкнулся с увлекательным классом в библиотеке 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?

Спасибо за любую информацию.Изучение этого класса помогло мне многому научиться.

1 Ответ

4 голосов
/ 04 июня 2019

Автор пытается помочь вам избежать случайных проблем с производительностью, которые возникают при повторном хешировании строк. Поскольку хеширование строк стоит дорого, вы, вероятно, захотите сделать это один раз и кэшировать его где-нибудь. Если у них есть неявный конструктор, вы можете неоднократно хэшировать одну и ту же строку, не зная или не намереваясь сделать это.

Таким образом, библиотека предоставляет неявную конструкцию для строковых литералов, которая может быть вычислена во время компиляции с помощью 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. Теперь при поиске ключей на карте нужно просто сравнить предварительно вычисленные значения хеш-ключей для ключей и строку поиска.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...