From b1d45485191fd4dd2e98bde5e11a3ac4fa50fbfd Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Wed, 10 Oct 2018 04:40:36 +0700 Subject: [PATCH] basic_string_hash class --- headers/enduro2d/utils/_utils.hpp | 13 +- headers/enduro2d/utils/strings.hpp | 54 +++++++ headers/enduro2d/utils/strings.inl | 170 ++++++++++++++++++++++ sources/enduro2d/utils/strings.cpp | 20 +++ untests/sources/untests_utils/strings.cpp | 39 +++++ 5 files changed, 294 insertions(+), 2 deletions(-) diff --git a/headers/enduro2d/utils/_utils.hpp b/headers/enduro2d/utils/_utils.hpp index e781757f..1e858619 100644 --- a/headers/enduro2d/utils/_utils.hpp +++ b/headers/enduro2d/utils/_utils.hpp @@ -24,6 +24,9 @@ namespace e2d template < typename T > class module; + + template < typename Char > + class basic_string_hash; } namespace e2d @@ -38,6 +41,11 @@ namespace e2d using str16_view = basic_string_view; using str32_view = basic_string_view; + using str_hash = basic_string_hash; + using wstr_hash = basic_string_hash; + using str16_hash = basic_string_hash; + using str32_hash = basic_string_hash; + struct seconds_tag {}; struct milliseconds_tag {}; struct microseconds_tag {}; @@ -53,10 +61,11 @@ namespace e2d namespace e2d { class noncopyable { + public: + noncopyable(const noncopyable&) = delete; + noncopyable& operator=(const noncopyable&) = delete; protected: noncopyable() = default; ~noncopyable() = default; - noncopyable(const noncopyable&) = delete; - noncopyable& operator=(const noncopyable&) = delete; }; } diff --git a/headers/enduro2d/utils/strings.hpp b/headers/enduro2d/utils/strings.hpp index 877cf7ed..d9bed535 100644 --- a/headers/enduro2d/utils/strings.hpp +++ b/headers/enduro2d/utils/strings.hpp @@ -10,6 +10,55 @@ #include "_utils.hpp" +namespace e2d +{ + template < typename Char > + class basic_string_hash final { + public: + static std::size_t empty_hash() noexcept; + public: + basic_string_hash() noexcept; + ~basic_string_hash() noexcept; + + basic_string_hash(basic_string_hash&& other) noexcept; + basic_string_hash& operator=(basic_string_hash&& other) noexcept; + + basic_string_hash(const basic_string_hash& other) noexcept; + basic_string_hash& operator=(const basic_string_hash& other) noexcept; + + explicit basic_string_hash(basic_string_view str) noexcept; + + basic_string_hash& assign(basic_string_hash&& other) noexcept; + basic_string_hash& assign(const basic_string_hash& other) noexcept; + basic_string_hash& assign(basic_string_view str) noexcept; + + void swap(basic_string_hash& other) noexcept; + void clear() noexcept; + bool empty() const noexcept; + + std::size_t hash() const noexcept; + private: + static std::size_t calculate_hash( + basic_string_view str) noexcept; + static void debug_check_collisions( + std::size_t hash, basic_string_view str) noexcept; + private: + std::size_t hash_ = empty_hash(); + }; + + template < typename Char > + void swap(basic_string_hash& l, basic_string_hash& r) noexcept; + + template < typename Char > + bool operator<(basic_string_hash l, basic_string_hash r) noexcept; + + template < typename Char > + bool operator==(basic_string_hash l, basic_string_hash r) noexcept; + + template < typename Char > + bool operator!=(basic_string_hash l, basic_string_hash r) noexcept; +} + namespace e2d { str make_utf8(str_view src); @@ -32,6 +81,11 @@ namespace e2d str32 make_utf32(str16_view src); str32 make_utf32(str32_view src); + str_hash make_hash(str_view src); + wstr_hash make_hash(wstr_view src); + str16_hash make_hash(str16_view src); + str32_hash make_hash(str32_view src); + namespace strings { class format_error; diff --git a/headers/enduro2d/utils/strings.inl b/headers/enduro2d/utils/strings.inl index 3f942315..42410251 100644 --- a/headers/enduro2d/utils/strings.inl +++ b/headers/enduro2d/utils/strings.inl @@ -11,6 +11,176 @@ #include "_utils.hpp" #include "strings.hpp" +namespace e2d +{ + template < typename Char > + std::size_t basic_string_hash::empty_hash() noexcept { + static std::size_t hash = calculate_hash(basic_string_view()); + return hash; + } + + template < typename Char > + basic_string_hash::basic_string_hash() noexcept = default; + + template < typename Char > + basic_string_hash::~basic_string_hash() noexcept = default; + + template < typename Char > + basic_string_hash::basic_string_hash( + basic_string_hash&& other) noexcept + { + assign(std::move(other)); + } + + template < typename Char > + basic_string_hash& basic_string_hash::operator=( + basic_string_hash&& other) noexcept + { + return assign(std::move(other)); + } + + template < typename Char > + basic_string_hash::basic_string_hash( + const basic_string_hash& other) noexcept + { + assign(other); + } + + template < typename Char > + basic_string_hash& basic_string_hash::operator=( + const basic_string_hash& other) noexcept + { + return assign(other); + } + + template < typename Char > + basic_string_hash::basic_string_hash( + basic_string_view str) noexcept + { + assign(str); + } + + template < typename Char > + basic_string_hash& basic_string_hash::assign( + basic_string_hash&& other) noexcept + { + if ( this != &other ) { + swap(other); + other.clear(); + } + return *this; + } + + template < typename Char > + basic_string_hash& basic_string_hash::assign( + const basic_string_hash& other) noexcept + { + if ( this != &other ) { + hash_ = other.hash_; + } + return *this; + } + + template < typename Char > + basic_string_hash& basic_string_hash::assign( + basic_string_view str) noexcept + { + hash_ = calculate_hash(str); + return *this; + } + + template < typename Char > + void basic_string_hash::swap(basic_string_hash& other) noexcept { + using std::swap; + swap(hash_, other.hash_); + } + + template < typename Char > + void basic_string_hash::clear() noexcept { + hash_ = empty_hash(); + } + + template < typename Char > + bool basic_string_hash::empty() const noexcept { + return hash_ == empty_hash(); + } + + template < typename Char > + std::size_t basic_string_hash::hash() const noexcept { + return hash_; + } + + template < typename Char > + std::size_t basic_string_hash::calculate_hash( + basic_string_view str) noexcept + { + std::size_t hash = std::hash>()(str); + debug_check_collisions(hash, str); + return hash; + } + + template < typename Char > + void basic_string_hash::debug_check_collisions( + std::size_t hash, basic_string_view str) noexcept + { + #if defined(E2D_BUILD_MODE) && E2D_BUILD_MODE == E2D_BUILD_MODE_DEBUG + try { + static std::mutex mutex; + static hash_map> table; + std::lock_guard guard(mutex); + const auto iter = table.find(hash); + if ( iter != table.end() ) { + E2D_ASSERT_MSG( + iter->second == str, + "basic_string_hash: hash collision detected"); + } else { + table.insert(std::make_pair(hash, str)); + } + } catch (...) { + E2D_ASSERT_MSG(false, "basic_string_hash: unexpected exception"); + throw; + } + #else + E2D_UNUSED(hash, str); + #endif + } +} + +namespace e2d +{ + template < typename Char > + void swap(basic_string_hash& l, basic_string_hash& r) noexcept { + l.swap(r); + } + + template < typename Char > + bool operator<(basic_string_hash l, basic_string_hash r) noexcept { + return l.hash() < r.hash(); + } + + template < typename Char > + bool operator==(basic_string_hash l, basic_string_hash r) noexcept { + return l.hash() == r.hash(); + } + + template < typename Char > + bool operator!=(basic_string_hash l, basic_string_hash r) noexcept { + return !(l == r); + } +} + +namespace std +{ + template < typename Char > + struct hash> + : std::unary_function, std::size_t> + { + std::size_t operator()(e2d::basic_string_hash hs) const noexcept { + return hs.hash(); + } + }; +} + namespace e2d { namespace strings { // diff --git a/sources/enduro2d/utils/strings.cpp b/sources/enduro2d/utils/strings.cpp index 794e7689..412347e3 100644 --- a/sources/enduro2d/utils/strings.cpp +++ b/sources/enduro2d/utils/strings.cpp @@ -238,6 +238,26 @@ namespace e2d ? str32() : str32(src.cbegin(), src.cend()); } + + // + // make_hash + // + + str_hash make_hash(str_view src) { + return str_hash(src); + } + + wstr_hash make_hash(wstr_view src) { + return wstr_hash(src); + } + + str16_hash make_hash(str16_view src) { + return str16_hash(src); + } + + str32_hash make_hash(str32_view src) { + return str32_hash(src); + } } namespace e2d { namespace strings diff --git a/untests/sources/untests_utils/strings.cpp b/untests/sources/untests_utils/strings.cpp index 3e1cdb3d..03030de7 100644 --- a/untests/sources/untests_utils/strings.cpp +++ b/untests/sources/untests_utils/strings.cpp @@ -18,6 +18,45 @@ namespace } TEST_CASE("strings") { + { + REQUIRE(str_hash().empty()); + REQUIRE(str_hash("").empty()); + REQUIRE_FALSE(str_hash("1").empty()); + + REQUIRE(str_hash().hash() == str_hash().hash()); + REQUIRE(str_hash().hash() == str_hash({null_utf8,0}).hash()); + REQUIRE(str_hash().hash() == wstr_hash({null_wide,0}).hash()); + REQUIRE(str_hash().hash() == str16_hash({null_utf16,0}).hash()); + REQUIRE(str_hash().hash() == str32_hash({null_utf32,0}).hash()); + REQUIRE(str_hash("hello").hash() == str_hash("hello").hash()); + REQUIRE(str_hash("world").hash() == str_hash("world").hash()); + REQUIRE(str_hash("hello").hash() != str_hash("world").hash()); + + REQUIRE(str_hash() == str_hash()); + REQUIRE(str_hash() == make_hash({null_utf8,0})); + REQUIRE(wstr_hash() == make_hash({null_wide,0})); + REQUIRE(str16_hash() == make_hash({null_utf16,0})); + REQUIRE(str32_hash() == make_hash({null_utf32,0})); + REQUIRE(str_hash("hello") == make_hash("hello")); + REQUIRE(str_hash("world") == make_hash("world")); + REQUIRE(str_hash("hello") != make_hash("world")); + + REQUIRE(str_hash("hello").assign("world") == make_hash("world")); + REQUIRE(str_hash("hello").assign(make_hash("world")) == make_hash("world")); + { + str_hash s("hello"); + s.clear(); + REQUIRE(s.empty()); + REQUIRE(s == str_hash()); + } + { + str_hash s1("hello"); + str_hash s2("world"); + s1.swap(s2); + REQUIRE(s2 == str_hash("hello")); + REQUIRE(s1 == make_hash("world")); + } + } { REQUIRE(make_utf8("hello") == "hello"); REQUIRE(make_utf8(L"hello") == "hello");