├── .clang-format ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── basic_database.hpp ├── config.hpp ├── config.hpp.in ├── database.cpp ├── database.hpp ├── error.cpp ├── error.hpp ├── example └── main.cpp ├── generator.cpp ├── generator.hpp ├── hash.hpp ├── string_id.cpp └── string_id.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -4 2 | AlignAfterOpenBracket: Align 3 | AlignConsecutiveAssignments: true 4 | AlignConsecutiveDeclarations: true 5 | AlignEscapedNewlinesLeft: Right 6 | AlignOperands: true 7 | AlignTrailingComments: true 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: false 10 | AllowShortCaseLabelsOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: Empty 12 | AllowShortIfStatementsOnASingleLine: false 13 | AllowShortLoopsOnASingleLine: false 14 | AlwaysBreakAfterReturnType: None 15 | AlwaysBreakBeforeMultilineStrings: false 16 | AlwaysBreakTemplateDeclarations: true 17 | BinPackArguments: true 18 | BinPackParameters: true 19 | BreakBeforeBraces: Custom 20 | BraceWrapping: 21 | AfterClass: true 22 | AfterControlStatement: true 23 | AfterEnum: true 24 | AfterFunction: true 25 | AfterNamespace: true 26 | AfterStruct: true 27 | AfterUnion: true 28 | AfterExternBlock: true 29 | BeforeCatch: true 30 | BeforeElse: true 31 | SplitEmptyFunction: false 32 | SplitEmptyRecord: false 33 | SplitEmptyNamespace: false 34 | BreakBeforeBinaryOperators: All 35 | BreakBeforeTernaryOperators: true 36 | BreakConstructorInitializers: BeforeColon 37 | BreakStringLiterals: false 38 | ColumnLimit: 100 39 | CompactNamespaces: true 40 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 41 | ConstructorInitializerIndentWidth: 0 42 | ContinuationIndentWidth: 4 43 | Cpp11BracedListStyle: true 44 | DerivePointerAlignment: false 45 | FixNamespaceComments: true 46 | IncludeBlocks: Preserve 47 | IndentCaseLabels: false 48 | IndentPPDirectives: AfterHash 49 | IndentWidth: 4 50 | IndentWrappedFunctionNames: true 51 | KeepEmptyLinesAtTheStartOfBlocks: false 52 | Language: Cpp 53 | MaxEmptyLinesToKeep: 1 54 | NamespaceIndentation: Inner 55 | PenaltyBreakBeforeFirstCallParameter: 19937 56 | PenaltyReturnTypeOnItsOwnLine: 19937 57 | PointerAlignment: Left 58 | ReflowComments: true 59 | SortIncludes: true 60 | SortUsingDeclarations: true 61 | SpaceAfterCStyleCast: false 62 | SpaceAfterTemplateKeyword: true 63 | SpaceBeforeAssignmentOperators: true 64 | SpaceBeforeParens: ControlStatements 65 | SpaceInEmptyParentheses: false 66 | SpacesBeforeTrailingComments: 1 67 | SpacesInAngles: false 68 | SpacesInCStyleCastParentheses: false 69 | SpacesInParentheses: false 70 | SpacesInSquareBrackets: false 71 | Standard: Cpp11 72 | TabWidth: 4 73 | UseTab: Never 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bii/ 2 | *.*~ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2.0-3 2 | ----- 3 | * bugfix of hashing 4 | * silence clang-tidy warning 5 | * remove biicode support 6 | 7 | 2.0-2 8 | ----- 9 | * guaranteed support for more compilers 10 | * improved CMakeLists.txt 11 | 12 | 2.0-1 13 | ----- 14 | * updated support for newer Biicode versions 15 | * improved CMakeLists.txt 16 | * compatibility mode to support MSVC 17 | 18 | 2.0 19 | --- 20 | * added interface base class for databases 21 | * allows implementing custom databases and different types for each string_id 22 | * added methods for generating string identifiers 23 | * internal changes in database implementation for performance improvements 24 | * support for non-Biicode builds 25 | 26 | 1.1 27 | --- 28 | * removed global database to allow multiple 29 | * added option to enable non-threadsafe database 30 | * improved CMakeLists.txt 31 | 32 | 1.0 33 | --- 34 | * initial state 35 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(foonathan_string_id) 3 | 4 | include(CheckCXXSourceCompiles) 5 | include(CheckCXXCompilerFlag) 6 | CHECK_CXX_COMPILER_FLAG(-std=c++11 cpp11_flag) 7 | if (cpp11_flag) 8 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 9 | else() 10 | CHECK_CXX_COMPILER_FLAG(-std=c++0x cpp0x_flag) 11 | if (cpp0x_flag) 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") 13 | endif(cpp0x_flag) 14 | endif(cpp11_flag) 15 | 16 | CHECK_CXX_SOURCE_COMPILES("constexpr auto foo = 1; int main(){}" comp_constexpr) 17 | CHECK_CXX_SOURCE_COMPILES("void foo() noexcept {} int main(){}" comp_noexcept) 18 | CHECK_CXX_SOURCE_COMPILES("#include 19 | int operator\"\"_foo(const char *, std::size_t){return 0;} int main(){}" comp_literal) 20 | CHECK_CXX_SOURCE_COMPILES("struct base {virtual void foo() {}}; 21 | struct derived : base {void foo() override {}}; 22 | int main(){}" comp_override) 23 | 24 | option(FOONATHAN_STRING_ID_DATABASE "enable or disable database" ON) 25 | option(FOONATHAN_STRING_ID_MULTITHREADED "enable or disable a thread safe database" ON) 26 | option(FOONATHAN_IMPL_HAS_CONSTEXPR "whether or not constexpr is supported" ${comp_constexpr}) 27 | option(FOONATHAN_IMPL_HAS_NOEXCEPT "whether or not noexcept is supported" ${comp_noexcept}) 28 | option(FOONATHAN_IMPL_HAS_LITERAL "whether or not literal operator overloading is supported" ${comp_literal}) 29 | option(FOONATHAN_IMPL_HAS_OVERRIDE "whether or not override is supported" ${comp_override}) 30 | if (NOT CMAKE_COMPILER_IS_GNUCXX) 31 | set(atomic_handler ON CACHE INTERNAL "") 32 | elseif(CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.7 OR 33 | CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.7) 34 | set(atomic_handler ON CACHE INTERNAL "") 35 | else() 36 | # not supported on GCC <= 4.6 37 | set(atomic_handler OFF CACHE INTERNAL "") 38 | endif(NOT CMAKE_COMPILER_IS_GNUCXX) 39 | option(FOONATHAN_STRING_ID_ATOMIC_HANDLER "whether or not handler functions are atomic" ${atomic_handler}) 40 | 41 | set(version_major 2 CACHE INTERNAL "") 42 | set(version_minor 0 CACHE INTERNAL "") 43 | 44 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.in" 45 | "${CMAKE_CURRENT_BINARY_DIR}/config_impl.hpp") 46 | 47 | set(src basic_database.hpp 48 | config.hpp 49 | database.hpp 50 | database.cpp 51 | error.cpp 52 | error.hpp 53 | generator.cpp 54 | generator.hpp 55 | hash.hpp 56 | string_id.cpp 57 | string_id.hpp 58 | CACHE INTERNAL "") 59 | 60 | add_library(foonathan_string_id ${src}) 61 | add_executable(foonathan_string_id_example example/main.cpp) 62 | target_link_libraries(foonathan_string_id_example PUBLIC foonathan_string_id) 63 | 64 | set(targets foonathan_string_id foonathan_string_id_example CACHE INTERNAL "") 65 | 66 | set_target_properties(${targets} PROPERTIES CXX_STANDARD 11) 67 | 68 | foreach(target ${targets}) 69 | target_include_directories(${target} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) 70 | endforeach() 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014-2015 Jonathan Müller 2 | 3 | This software is provided 'as-is', without any express or 4 | implied warranty. In no event will the authors be held 5 | liable for any damages arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute 9 | it freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; 12 | you must not claim that you wrote the original software. 13 | If you use this software in a product, an acknowledgment 14 | in the product documentation would be appreciated but 15 | is not required. 16 | 17 | 2. Altered source versions must be plainly marked as such, 18 | and must not be misrepresented as being the original software. 19 | 20 | 3. This notice may not be removed or altered from any 21 | source distribution. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | string id 2 | ========= 3 | 4 | ![Project Status](https://img.shields.io/endpoint?url=https%3A%2F%2Fwww.jonathanmueller.dev%2Fproject%2Fstring_id%2Findex.json) 5 | 6 | Motivation 7 | ---------- 8 | It is often useful for logging purposes in real-time applications like games to give each entity a name. This makes tracking down errors easier because finding entities via unique names is easier than looking at unique numbers or other forms of identifiers. But strings are huge and copying and comparing them is slow, so they often can't be used in performance critical code. 9 | 10 | One solution are hashed strings which are only integers and thus small, fast to copy and compare. But hashes don't allow retrieving the original string value which is exactly what is needed for logging and debugging purposes! In addition, there is a chance of collisions, so equal hash code doesn't necessarily mean equal strings. This chance is small, but a source for hard to find bugs. 11 | 12 | Another solution is string interning. String interning uses a global look-up table where each string is stored only once and is referenced via an index or something similar. Copying and comparing is fast, too, but they are still not perfect: You can only access them at runtime. Getting the value at compile-time e.g. for a switch is not possible. 13 | 14 | So on the one hand we want fast and lightweight identifiers but on the other hand also methods to get the name back. 15 | 16 | 17 | string_id 18 | --------- 19 | This open source library provides a mix between the two solutions in the form of the class *string_id*. Each object stores a hashed string value and a pointer to the database in which the original string value is stored. This allows retrieving the string value when needed while also getting the performance benefits from hashed strings. In addition, the database can detect collisions which can be handled via a custom collision handler. There is a user-defined literal to create a compile-time hashed string value to use it as a constant expression. 20 | 21 | The database can be any user-defined type derived from a certain interface class. There are several pre-defined databases. This includes a dummy database that does not store anything, a adapter for other databases to make them threadsafe and a highly optimized database for efficient storing and retrieving of strings. A typedef *default_database* is one of those databases and can be set via the following CMake options: 22 | 23 | * *FOONATHAN_STRING_ID_DATABASE* - if *OFF*, the database is disabled completely, e.g. the dummy database is used. This does not allow retrieving strings or collision checking but does not need that much memory. It is *ON* by default. 24 | 25 | * *FOONATHAN_STRING_ID_MULTITHREADED* - if *ON*, database access will be synchronized via a mutex, e.g. the thread safe adapter will be used. It has no effect if database is disabled. Default value is *ON*. 26 | 27 | There are special generator classes. They have a similar interface to the random number generators in the standard libraries, but generate string identifiers. This is used to generate a bunch of identifiers in an automated fashion. The generators also take care that there are always new identifiers generated. This can be controlled via a handler similar to the collision handling, too. 28 | 29 | See example/main.cpp for an example. 30 | 31 | Hashing and Databases 32 | --------------------- 33 | It currently uses a FNV-1a 64bit hash. Collisions are really rare, I have tested 219,606 English words (in lowercase) mixed with a bunch of numbers and didn't encounter a single collision. Since this is the normal use case for identifiers, the hash function is pretty good. In addition, there is a good distribution of the hashed values and it is easy to calculate. 34 | 35 | The database uses a specialized hash table. Collisions of the bucket index are resolved via separate chaining with single linked list. Each node contains the string directly without additional memory allocation. The nodes on the linked list are sorted using the hash value. This allows efficient retrieving and checking whether there is already a string with the same hash value stored. This makes it very efficient and faster than the std::unordered_map that was used before (at least faster than libstdc++ implementation I have used for the benchmarks). 36 | 37 | Compiler Support 38 | ---------------- 39 | This library has been compiled under the following compilers: 40 | * GCC 4.6-4.9 on Linux 41 | * clang 3.4-3.5 on Linux 42 | * Visual Studio 12 on Windows 43 | 44 | There are the compatibility options and replacement marcos for constexpr, noexcept, override and literal operators. Atomic handler functions can be disabled optionally and are off for GCC 4.6 by default since it does not support them. 45 | -------------------------------------------------------------------------------- /basic_database.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #ifndef FOONATHAN_STRING_ID_BASIC_DATABASE_HPP_INCLUDED 6 | #define FOONATHAN_STRING_ID_BASIC_DATABASE_HPP_INCLUDED 7 | 8 | #include "config.hpp" 9 | #include "hash.hpp" 10 | 11 | namespace foonathan { namespace string_id 12 | { 13 | /// \brief The interface for all databases. 14 | /// \detail You can derive own databases from it. 15 | class basic_database 16 | { 17 | public: 18 | /// @{ 19 | /// \brief Databases are not copy- or moveable. 20 | /// \detail You must not write a swap function either!
21 | /// This has implementation reasons. 22 | basic_database(const basic_database &) = delete; 23 | basic_database(basic_database &&) = delete; 24 | /// @} 25 | 26 | virtual ~basic_database() = default; 27 | 28 | /// \brief The status of an insert operation. 29 | enum insert_status 30 | { 31 | /// \brief Two different strings collide on the same value. 32 | collision, 33 | /// \brief A new string was inserted. 34 | new_string, 35 | /// \brief The string already existed inside the database. 36 | old_string 37 | }; 38 | 39 | /// \brief Should insert a new hash-string-pair with prefix (optional) into the internal database. 40 | /// \detail The string must be copied prior to storing, it may not stay valid. 41 | /// \arg \c hash is the hash of the string. 42 | /// \arg \c str is the string which does not need to be null-terminated. 43 | /// \arg \c length is the length of the string. 44 | /// \return The \ref insert_status. 45 | virtual insert_status insert(hash_type hash, const char* str, std::size_t length) = 0; 46 | 47 | /// \brief Inserts a hash-string-pair with given prefix into the internal database. 48 | /// \detail The default implementation performs a lookup of the prefix string and appends it, 49 | /// then it calls \ref insert.
50 | /// Override it if you can do it more efficiently. 51 | /// \arg \c hash is the hash of the string plus prefix. 52 | /// \arg \c prefix is the hash of the prefix-string. 53 | /// \arg \c str is the suffix which does not need to be null-terminated. 54 | /// \arg \c length is the length of the suffix. 55 | /// \return The \ref insert_status. 56 | virtual insert_status insert_prefix(hash_type hash, hash_type prefix, 57 | const char *str, std::size_t length); 58 | 59 | /// \brief Should return the string stored with a given hash. 60 | /// \detail It is guaranteed that the hash value has been inserted before. 61 | /// \return A null-terminated string belonging to the hash code or 62 | /// an error message if the database does not store anything.
63 | /// The return value must stay valid as long as the database exists. 64 | virtual const char* lookup(hash_type hash) const FOONATHAN_NOEXCEPT = 0; 65 | 66 | protected: 67 | basic_database() = default; 68 | }; 69 | }} // foonathan::string_id 70 | 71 | #endif // FOONATHAN_STRING_ID_BASIC_DATABASE_HPP_INCLUDED 72 | -------------------------------------------------------------------------------- /config.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | // dummy header including the real config file from the CMake binary dir 6 | 7 | #ifndef FOONATHAN_STRING_ID_CONFIG_HPP_INCLUDED 8 | #define FOONATHAN_STRING_ID_CONFIG_HPP_INCLUDED 9 | 10 | #include "config_impl.hpp" 11 | 12 | #endif // FOONATHAN_STRING_ID_CONFIG_HPP_INCLUDED 13 | -------------------------------------------------------------------------------- /config.hpp.in: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | //=== version ===// 6 | /// \brief Major version. 7 | #define FOONATHAN_STRING_ID_VERSION_MAJOR ${version_major} 8 | 9 | /// \brief Minor version. 10 | #define FOONATHAN_STRING_ID_VERSION_MINOR ${version_minor} 11 | 12 | /// \brief Total version number. 13 | #define FOONATHAN_STRING_ID_VERSION (${version_major} * 100 + ${version_minor}) 14 | 15 | //=== database ===// 16 | /// \brief Whether or not the database for string ids is active. 17 | /// \detail This is \c true by default, change it via CMake option \c FOONATHAN_STRING_ID_DATABASE. 18 | #cmakedefine01 FOONATHAN_STRING_ID_DATABASE 19 | 20 | /// \brief Whether or not the database should thread safe. 21 | /// \detail This is \c true by default, change it via CMake option \c FOONATHAN_STRING_ID_MULTITHREADED. 22 | #cmakedefine01 FOONATHAN_STRING_ID_MULTITHREADED 23 | 24 | //=== compatibility ===// 25 | #cmakedefine01 FOONATHAN_IMPL_HAS_NOEXCEPT 26 | #cmakedefine01 FOONATHAN_IMPL_HAS_CONSTEXPR 27 | #cmakedefine01 FOONATHAN_IMPL_HAS_LITERAL 28 | #cmakedefine01 FOONATHAN_IMPL_HAS_OVERRIDE 29 | 30 | #ifndef FOONATHAN_NOEXCEPT 31 | #if FOONATHAN_IMPL_HAS_NOEXCEPT 32 | #define FOONATHAN_NOEXCEPT noexcept 33 | #else 34 | #define FOONATHAN_NOEXCEPT 35 | #endif 36 | #endif 37 | 38 | #ifndef FOONATHAN_CONSTEXPR 39 | #if FOONATHAN_IMPL_HAS_CONSTEXPR 40 | #define FOONATHAN_CONSTEXPR constexpr 41 | #else 42 | #define FOONATHAN_CONSTEXPR const 43 | #endif 44 | #endif 45 | 46 | #ifndef FOONATHAN_CONSTEXPR_FNC 47 | #if FOONATHAN_IMPL_HAS_CONSTEXPR 48 | #define FOONATHAN_CONSTEXPR_FNC constexpr 49 | #else 50 | #define FOONATHAN_CONSTEXPR_FNC inline 51 | #endif 52 | #endif 53 | 54 | #ifndef FOONATHAN_OVERRIDE 55 | #if FOONATHAN_IMPL_HAS_OVERRIDE 56 | #define FOONATHAN_OVERRIDE override 57 | #else 58 | #define FOONATHAN_OVERRIDE 59 | #endif 60 | #endif 61 | 62 | /// \brief Whether or not the \c constexpr literal operators are availble. 63 | /// \detail If this is \c false, there is only the \ref id() function which can't be used inside switch cases. 64 | #define FOONATHAN_STRING_ID_HAS_LITERAL (FOONATHAN_IMPL_HAS_LITERAL && FOONATHAN_IMPL_HAS_CONSTEXPR) 65 | 66 | /// \brief Whether or not the handler functions are atomic. 67 | #cmakedefine01 FOONATHAN_STRING_ID_ATOMIC_HANDLER 68 | -------------------------------------------------------------------------------- /database.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #include "database.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace sid = foonathan::string_id; 13 | 14 | sid::basic_database::insert_status sid::basic_database::insert_prefix(hash_type hash, hash_type prefix, 15 | const char *str, std::size_t length) 16 | { 17 | std::string prefix_str = lookup(prefix); 18 | return insert(hash, (prefix_str + str).c_str(), prefix_str.size() + length); 19 | } 20 | 21 | namespace 22 | { 23 | // equivalent to prefix + str == other_str for std::string 24 | // prefix and other_str are null-terminated 25 | bool strequal(const char *prefix, 26 | const char *str, std::size_t length, const char *other_str) FOONATHAN_NOEXCEPT 27 | { 28 | assert(prefix); 29 | while (*prefix) 30 | if (*prefix++ != *other_str++) 31 | return false; 32 | return std::strncmp(str, other_str, length) == 0; 33 | } 34 | } 35 | 36 | /// \cond impl 37 | class sid::map_database::node_list 38 | { 39 | struct node 40 | { 41 | std::size_t length; // length of string 42 | hash_type hash; 43 | node *next; 44 | 45 | node(const char *str, std::size_t length, 46 | hash_type h, node *next) FOONATHAN_NOEXCEPT 47 | : length(length), hash(h), next(next) 48 | { 49 | void* mem = this; 50 | auto dest = static_cast(mem) + sizeof(node); 51 | std::strncpy(dest, str, length); 52 | dest[length] = 0; 53 | } 54 | 55 | node(const char *prefix, std::size_t length_prefix, 56 | const char *str, std::size_t length_string, 57 | hash_type h, node *next) FOONATHAN_NOEXCEPT 58 | : length(length_prefix + length_string), hash(h), next(next) 59 | { 60 | void* mem = this; 61 | auto dest = static_cast(mem) + sizeof(node); 62 | std::strncpy(dest, prefix, length_prefix); 63 | dest += length_prefix; 64 | std::strncpy(dest, str, length_string); 65 | dest[length_string] = 0; 66 | } 67 | 68 | const char* get_str() const FOONATHAN_NOEXCEPT 69 | { 70 | const void *mem = this; 71 | return static_cast(mem) + sizeof(node); 72 | } 73 | }; 74 | 75 | struct insert_pos_t 76 | { 77 | bool exists; // if true: cur set, else: prev and next set 78 | 79 | node*& prev; 80 | node* next; 81 | 82 | node* cur; 83 | 84 | insert_pos_t(node*& prev, node *next) FOONATHAN_NOEXCEPT 85 | : exists(false), prev(prev), next(next), cur(nullptr) {} 86 | 87 | insert_pos_t(node *cur) FOONATHAN_NOEXCEPT 88 | : exists(true), prev(this->cur), next(nullptr), cur(cur) {} 89 | }; 90 | 91 | public: 92 | node_list() FOONATHAN_NOEXCEPT 93 | : head_(nullptr) {} 94 | 95 | ~node_list() FOONATHAN_NOEXCEPT 96 | { 97 | auto cur = head_; 98 | while (cur) 99 | { 100 | auto next = cur->next; 101 | ::operator delete(cur); 102 | cur = next; 103 | } 104 | } 105 | 106 | basic_database::insert_status insert(hash_type hash, const char *str, std::size_t length) 107 | { 108 | auto pos = insert_pos(hash); 109 | if (pos.exists) 110 | return std::strncmp(str, pos.cur->get_str(), length) == 0 ? 111 | basic_database::old_string : basic_database::collision; 112 | auto mem = ::operator new(sizeof(node) + length + 1); 113 | auto n = ::new(mem) node(str, length, hash, pos.next); 114 | pos.prev = n; 115 | return basic_database::new_string; 116 | } 117 | 118 | basic_database::insert_status insert_prefix(node_list &prefix_bucket, hash_type prefix, 119 | hash_type hash, const char *str, std::size_t length) 120 | { 121 | auto prefix_node = prefix_bucket.find_node(prefix); 122 | auto pos = insert_pos(hash); 123 | if (pos.exists) 124 | return strequal(prefix_node->get_str(), str, length, pos.cur->get_str()) ? 125 | basic_database::old_string : basic_database::collision; 126 | auto mem = ::operator new(sizeof(node) + prefix_node->length + length + 1); 127 | auto n = ::new(mem) node(prefix_node->get_str(), prefix_node->length, 128 | str, length, hash, pos.next); 129 | pos.prev = n; 130 | return basic_database::new_string; 131 | } 132 | 133 | // inserts all nodes into new buckets, this list is empty afterwards 134 | void rehash(node_list *buckets, std::size_t size) FOONATHAN_NOEXCEPT 135 | { 136 | auto cur = head_; 137 | while (cur) 138 | { 139 | auto next = cur->next; 140 | auto pos = buckets[cur->hash % size].insert_pos(cur->hash); 141 | assert(!pos.exists && "element can't be there already"); 142 | pos.prev = cur; 143 | cur->next = pos.next; 144 | cur = next; 145 | } 146 | head_ = nullptr; 147 | } 148 | 149 | // returns element with hash, there must be one 150 | const char* lookup(hash_type h) const FOONATHAN_NOEXCEPT 151 | { 152 | return find_node(h)->get_str(); 153 | } 154 | 155 | private: 156 | node* find_node(hash_type h) const FOONATHAN_NOEXCEPT 157 | { 158 | assert(head_ && "hash not inserted"); 159 | auto cur = head_; 160 | while (cur->hash < h) 161 | { 162 | cur = cur->next; 163 | assert(cur && "hash not inserted"); 164 | } 165 | assert(cur->hash == h && "hash not inserted"); 166 | return cur; 167 | } 168 | 169 | insert_pos_t insert_pos(hash_type hash) FOONATHAN_NOEXCEPT 170 | { 171 | node *cur = head_, *prev = nullptr; 172 | while (cur && cur->hash <= hash) 173 | { 174 | if (cur->hash < hash) 175 | { 176 | prev = cur; 177 | cur = cur->next; 178 | } 179 | else if (cur->hash == hash) 180 | return {cur}; 181 | } 182 | return {prev ? prev->next : head_, cur}; 183 | } 184 | 185 | node *head_; 186 | }; 187 | /// \endcond 188 | 189 | sid::map_database::map_database(std::size_t size, double max_load_factor) 190 | : buckets_(new node_list[size]), 191 | no_items_(0u), no_buckets_(size), 192 | max_load_factor_(max_load_factor), 193 | next_resize_(static_cast(std::floor(no_buckets_ * max_load_factor_))) 194 | {} 195 | 196 | sid::map_database::~map_database() FOONATHAN_NOEXCEPT {} 197 | 198 | sid::basic_database::insert_status sid::map_database::insert(hash_type hash, const char *str, std::size_t length) 199 | { 200 | if (no_items_ + 1 >= next_resize_) 201 | rehash(); 202 | auto status = buckets_[hash % no_buckets_].insert(hash, str, length); 203 | if (status == insert_status::new_string) 204 | ++no_items_; 205 | return status; 206 | } 207 | 208 | sid::basic_database::insert_status sid::map_database::insert_prefix(hash_type hash, hash_type prefix, 209 | const char *str, std::size_t length) 210 | { 211 | if (no_items_ + 1 >= next_resize_) 212 | rehash(); 213 | auto status = buckets_[hash % no_buckets_].insert_prefix(buckets_[prefix % no_buckets_], prefix, 214 | hash, str, length); 215 | if (status == insert_status::new_string) 216 | ++no_items_; 217 | return status; 218 | } 219 | 220 | const char* sid::map_database::lookup(hash_type hash) const FOONATHAN_NOEXCEPT 221 | { 222 | return buckets_[hash % no_buckets_].lookup(hash); 223 | } 224 | 225 | void sid::map_database::rehash() 226 | { 227 | static FOONATHAN_CONSTEXPR auto growth_factor = 2; 228 | auto new_size = growth_factor * no_buckets_; 229 | auto buckets = new node_list[new_size](); 230 | auto end = buckets_.get() + no_buckets_; 231 | for (auto list = buckets_.get(); list != end; ++list) 232 | list->rehash(buckets, new_size); 233 | buckets_.reset(buckets); 234 | no_buckets_ = new_size; 235 | next_resize_ = static_cast(std::floor(no_buckets_ * max_load_factor_)); 236 | } 237 | -------------------------------------------------------------------------------- /database.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #ifndef FOONATHAN_STRING_ID_DATABASE_HPP_INCLUDED 6 | #define FOONATHAN_STRING_ID_DATABASE_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | 11 | #include "basic_database.hpp" 12 | #include "config.hpp" 13 | 14 | namespace foonathan { namespace string_id 15 | { 16 | /// \brief A database that doesn't store the string-values. 17 | /// \detail It does not detect collisions or allows retrieving, 18 | /// \c lookup() returns "string_id database disabled". 19 | class dummy_database : public basic_database 20 | { 21 | public: 22 | insert_status insert(hash_type, const char *, std::size_t) FOONATHAN_OVERRIDE 23 | { 24 | return new_string; 25 | } 26 | 27 | insert_status insert_prefix(hash_type, hash_type, const char *, std::size_t) FOONATHAN_OVERRIDE 28 | { 29 | return new_string; 30 | } 31 | 32 | const char* lookup(hash_type) const FOONATHAN_NOEXCEPT FOONATHAN_OVERRIDE 33 | { 34 | return "string_id database disabled"; 35 | } 36 | }; 37 | 38 | /// \brief A database that uses a highly optimized hash table. 39 | class map_database : public basic_database 40 | { 41 | public: 42 | /// \brief Creates a new database with given number of buckets and maximum load factor. 43 | explicit map_database(std::size_t size = 1024, double max_load_factor = 1.0); 44 | ~map_database() FOONATHAN_NOEXCEPT; 45 | 46 | insert_status insert(hash_type hash, const char *str, std::size_t length) FOONATHAN_OVERRIDE; 47 | insert_status insert_prefix(hash_type hash, hash_type prefix, 48 | const char *str, std::size_t length) FOONATHAN_OVERRIDE; 49 | const char* lookup(hash_type hash) const FOONATHAN_NOEXCEPT FOONATHAN_OVERRIDE; 50 | 51 | private: 52 | void rehash(); 53 | 54 | class node_list; 55 | std::unique_ptr buckets_; 56 | std::size_t no_items_, no_buckets_; 57 | double max_load_factor_; 58 | std::size_t next_resize_; 59 | }; 60 | 61 | /// \brief A thread-safe database adapter. 62 | /// \detail It derives from any database type and synchronizes access via \c std::mutex. 63 | template 64 | class thread_safe_database : public Database 65 | { 66 | public: 67 | /// \brief The base database. 68 | typedef Database base_database; 69 | 70 | // workaround of lacking inheriting constructors 71 | template 72 | explicit thread_safe_database(Args&&... args) 73 | : base_database(std::forward(args)...) {} 74 | 75 | typename Database::insert_status 76 | insert(hash_type hash, const char *str, std::size_t length) FOONATHAN_OVERRIDE 77 | { 78 | std::lock_guard lock(mutex_); 79 | return Database::insert(hash, str, length); 80 | } 81 | 82 | typename Database::insert_status 83 | insert_prefix(hash_type hash, hash_type prefix, const char *str, std::size_t length) FOONATHAN_OVERRIDE 84 | { 85 | std::lock_guard lock(mutex_); 86 | return Database::insert_prefix(hash, prefix, str, length); 87 | } 88 | 89 | const char* lookup(hash_type hash) const FOONATHAN_NOEXCEPT FOONATHAN_OVERRIDE 90 | { 91 | std::lock_guard lock(mutex_); 92 | return Database::lookup(hash); 93 | } 94 | 95 | private: 96 | mutable std::mutex mutex_; 97 | }; 98 | 99 | /// \brief The default database where the strings are stored. 100 | /// \detail Its exact type is one of the previous listed databases. 101 | /// You can control its selection via the macros listed in config.hpp.in. 102 | #if FOONATHAN_STRING_ID_DATABASE && FOONATHAN_STRING_ID_MULTITHREADED 103 | typedef thread_safe_database default_database; 104 | #elif FOONATHAN_STRING_ID_DATABASE 105 | typedef map_database default_database; 106 | #else 107 | typedef dummy_database default_database; 108 | #endif 109 | }} // namespace foonathan::string_id 110 | 111 | #endif // FOONATHAN_STRING_ID_DATABASE_HPP_INCLUDED 112 | -------------------------------------------------------------------------------- /error.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #include "error.hpp" 6 | 7 | #include 8 | #include 9 | 10 | namespace sid = foonathan::string_id; 11 | 12 | namespace 13 | { 14 | void default_collision_handler(sid::hash_type hash, const char *a, const char *b) 15 | { 16 | throw sid::collision_error(hash, a, b); 17 | } 18 | 19 | #if FOONATHAN_STRING_ID_ATOMIC_HANDLER 20 | std::atomic collision_h(default_collision_handler); 21 | #else 22 | sid::collision_handler collision_h(default_collision_handler); 23 | #endif 24 | } 25 | 26 | sid::collision_handler sid::set_collision_handler(collision_handler h) 27 | { 28 | #if FOONATHAN_STRING_ID_ATOMIC_HANDLER 29 | return collision_h.exchange(h); 30 | #else 31 | auto val = collision_h; 32 | collision_h = h; 33 | return val; 34 | #endif 35 | } 36 | 37 | sid::collision_handler sid::get_collision_handler() 38 | { 39 | return collision_h; 40 | } 41 | 42 | const char* sid::collision_error::what() const FOONATHAN_NOEXCEPT try 43 | { 44 | return what_.c_str(); 45 | } 46 | catch (...) 47 | { 48 | return "foonathan::string_id::collision_error: two different strings are producing the same value"; 49 | } 50 | 51 | namespace 52 | { 53 | FOONATHAN_CONSTEXPR auto no_tries_generation = 8u; 54 | 55 | bool default_generation_error_handler(std::size_t no, const char *generator_name, 56 | sid::hash_type, const char *) 57 | { 58 | if (no >= no_tries_generation) 59 | throw sid::generation_error(generator_name); 60 | return true; 61 | } 62 | 63 | #if FOONATHAN_STRING_ID_ATOMIC_HANDLER 64 | std::atomic generation_error_h(default_generation_error_handler); 65 | #else 66 | sid::generation_error_handler generation_error_h(default_generation_error_handler); 67 | #endif 68 | } 69 | 70 | sid::generation_error_handler sid::set_generation_error_handler(generation_error_handler h) 71 | { 72 | #if FOONATHAN_STRING_ID_ATOMIC_HANDLER 73 | return generation_error_h.exchange(h); 74 | #else 75 | auto val = generation_error_h; 76 | generation_error_h = h; 77 | return val; 78 | #endif 79 | } 80 | 81 | sid::generation_error_handler sid::get_generation_error_handler() 82 | { 83 | return generation_error_h; 84 | } 85 | 86 | const char* sid::generation_error::what() const FOONATHAN_NOEXCEPT try 87 | { 88 | return what_.c_str(); 89 | } 90 | catch (...) 91 | { 92 | return "foonathan::string_id::generation_error: unable to generate new string id."; 93 | } 94 | -------------------------------------------------------------------------------- /error.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #ifndef FOONATHAN_STRING_ID_ERROR_HPP_INCLUDED 6 | #define FOONATHAN_STRING_ID_ERROR_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | 11 | #include "config.hpp" 12 | #include "hash.hpp" 13 | 14 | namespace foonathan { namespace string_id 15 | { 16 | /// \brief The base class for all custom exception classes of this library. 17 | class error : public std::exception 18 | { 19 | protected: 20 | error() = default; 21 | }; 22 | 23 | /// \brief The type of the collision handler. 24 | /// \detail It will be called when a string hashing results in a collision giving it the two strings collided. 25 | /// The default handler throws an exception of type \ref collision_error. 26 | typedef void(*collision_handler)(hash_type hash, const char *a, const char *b); 27 | 28 | /// \brief Exchanges the \ref collision_handler. 29 | /// \detail This function is thread safe if \ref FOONATHAN_STRING_ID_ATOMIC_HANDLER is \c true. 30 | collision_handler set_collision_handler(collision_handler h); 31 | 32 | /// \brief Returns the current \ref collision_handler. 33 | collision_handler get_collision_handler(); 34 | 35 | /// \brief The exception class thrown by the default \ref collision_handler. 36 | class collision_error : public error 37 | { 38 | public: 39 | //=== constructor/destructor ===// 40 | /// \brief Creates a new exception, same parameter as \ref collision_handler. 41 | collision_error(hash_type hash, const char *a, const char *b) 42 | : a_(a), b_(b), 43 | what_(R"(foonathan::string_id::collision_error: strings ")" + a_ + R"(" and ")" + b_ + 44 | R"(") are both producing the value )" + std::to_string(hash)), hash_(hash) {} 45 | 46 | ~collision_error() FOONATHAN_NOEXCEPT FOONATHAN_OVERRIDE {} 47 | 48 | //=== accessors ===// 49 | const char* what() const FOONATHAN_NOEXCEPT FOONATHAN_OVERRIDE; 50 | 51 | /// @{ 52 | /// \brief Returns one of the two strings that colllided. 53 | const char* first_string() const FOONATHAN_NOEXCEPT 54 | { 55 | return a_.c_str(); 56 | } 57 | 58 | const char* second_string() const FOONATHAN_NOEXCEPT 59 | { 60 | return b_.c_str(); 61 | } 62 | /// @} 63 | 64 | /// \brief Returns the hash code of the collided strings. 65 | hash_type hash_code() const FOONATHAN_NOEXCEPT 66 | { 67 | return hash_; 68 | } 69 | 70 | private: 71 | std::string a_, b_, what_; 72 | hash_type hash_; 73 | }; 74 | 75 | /// \brief The type of the generator error handler. 76 | /// \detail It will be called when a generator would generate a \ref string_id that already was generated. 77 | /// The generator will try again until the handler returns \c false in which case it just returns the old \c string_id. 78 | /// It passes the number of tries, the name of the generator and the hash and string of the generated \c string_id.
79 | /// The default handler allows 8 tries and then throws an exception of type \ref generation_error. 80 | typedef bool(*generation_error_handler)(std::size_t no, const char *generator_name, 81 | hash_type hash, const char *str); 82 | 83 | /// \brief Exchanges the \ref generation_error_handler. 84 | /// \detail This function is thread safe if \ref FOONATHAN_STRING_ID_ATOMIC_HANDLER is \c true. 85 | generation_error_handler set_generation_error_handler(generation_error_handler h); 86 | 87 | /// \brief Returns the current \ref generation_error_handler. 88 | generation_error_handler get_generation_error_handler(); 89 | 90 | /// \brief The exception class thrown by the default \ref generation_error_handler. 91 | class generation_error : public error 92 | { 93 | public: 94 | //=== constructor/destructor ===// 95 | /// \brief Creates it by giving it the name of the generator. 96 | generation_error(const char *generator_name) 97 | : name_(generator_name), what_("foonathan::string_id::generation_error: Generator \"" + name_ + 98 | "\" was unable to generate new string id.") {} 99 | 100 | ~generation_error() FOONATHAN_NOEXCEPT FOONATHAN_OVERRIDE {} 101 | 102 | //=== accessors ===// 103 | const char* what() const FOONATHAN_NOEXCEPT FOONATHAN_OVERRIDE; 104 | 105 | /// \brief Returns the name of the generator. 106 | const char* generator_name() const FOONATHAN_NOEXCEPT 107 | { 108 | return name_.c_str(); 109 | } 110 | 111 | private: 112 | std::string name_, what_; 113 | }; 114 | }} // namespace foonathan::string_id 115 | 116 | #endif // FOONATHAN_STRING_ID_ERROR_HPP_INCLUDED 117 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #include // std::time 6 | #include 7 | #include // std::mt19937 8 | #include 9 | 10 | #include "../database.hpp" // for the databases 11 | #include "../error.hpp" // for error handling 12 | #include "../generator.hpp" // for the generator classes 13 | #include "../string_id.hpp" // for the string_id 14 | 15 | // namespace alias 16 | namespace sid = foonathan::string_id; 17 | 18 | int main() try 19 | { 20 | // this allows using the literal 21 | using namespace sid::literals; 22 | 23 | // create database to store the strings in 24 | // it must stay valid as long as each string_id using it 25 | sid::default_database database; 26 | 27 | //=== string_id usage ===// 28 | // create an id 29 | sid::string_id sid("Test0815", database); 30 | std::cout << "Hash code " << sid.hash_code() << " belongs to string \"" << sid.string() << "\"\n"; 31 | // Output (Database supports retrieving): Hash code 16741300784925887095 belongs to string "Test0815" 32 | // Output (Database doesn't): Hash code 16741300784925887095 belongs to string "string_id database disabled" 33 | 34 | sid::string_id a("Hello", database), b("World", database); 35 | 36 | // compare two ids 37 | std::cout << std::boolalpha << (a == b) << '\n'; 38 | // Output: false 39 | 40 | // compare id with constant 41 | #if FOONATHAN_STRING_ID_HAS_LITERAL 42 | std::cout << (a == "Hello"_id) << '\n'; 43 | #else 44 | std::cout << (a == id("Hello")) << '\n'; 45 | #endif 46 | // Output: true 47 | 48 | #if FOONATHAN_STRING_ID_HAS_LITERAL 49 | // literal is compile-time 50 | switch (b.hash_code()) 51 | { 52 | case "Hello"_id: 53 | std::cout << "Hello\n"; 54 | break; 55 | case "world"_id: // case-sensitive 56 | std::cout << "world\n"; 57 | break; 58 | case "World"_id: 59 | std::cout << "World\n"; 60 | break; 61 | } 62 | #endif 63 | 64 | //=== generation ===// 65 | // the prefix for all generated ids 66 | sid::string_id prefix("entity-", database); 67 | try 68 | { 69 | // a generator type appending 8 random characters to the prefix 70 | // it uses the std::mt19937 generator for the actual generation 71 | typedef sid::random_generator generator_t; 72 | // create a generator, seed the random number generator with the current time 73 | generator_t generator(prefix, std::mt19937(std::int_fast32_t(std::time(nullptr)))); 74 | 75 | std::vector ids; 76 | for (auto i = 0; i != 10; ++i) 77 | // generate new identifier 78 | // it is guaranteed unique and will be stored in the database of the prefix 79 | ids.push_back(generator()); 80 | 81 | // print the name of all the ids 82 | for (auto &id : ids) 83 | std::cout << id.string() << '\n'; 84 | // possible generated name: entity-jXRnZAVG 85 | } 86 | catch (sid::generation_error &ex) 87 | { 88 | // the generator was unable to generate a new unique identifier (very unlikely) 89 | std::cerr << "[ERROR] " << ex.what() << '\n'; 90 | } 91 | 92 | try 93 | { 94 | // a generator appending an increasing number to the prefix 95 | typedef sid::counter_generator generator_t; 96 | 97 | // create a generator starting with 0, each number will be 4 digits long 98 | generator_t generator(prefix, 0, 4); 99 | 100 | std::vector ids; 101 | for (auto i = 0; i != 10; ++i) 102 | // generate new identifier 103 | // it is guaranteed unique and will be stored in the database of the prefix 104 | ids.push_back(generator()); 105 | 106 | // print the name of all the ids 107 | for (auto &id : ids) 108 | std::cout << id.string() << '\n'; 109 | // possible generated name: entity-0006 110 | } 111 | catch (sid::generation_error &ex) 112 | { 113 | // the generator was unable to generate a new unique identifier (very unlikely) 114 | std::cerr << "[ERROR] " << ex.what() << '\n'; 115 | } 116 | } 117 | catch (sid::collision_error &ex) 118 | { 119 | // two different strings are resulting in the same hash value (very unlikely) 120 | std::cerr << "[ERROR] " << ex.what() << '\n'; 121 | } 122 | -------------------------------------------------------------------------------- /generator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #include "generator.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "error.hpp" 12 | 13 | namespace sid = foonathan::string_id; 14 | 15 | bool sid::detail::handle_generation_error(std::size_t counter, const char *name, const string_id &result) 16 | { 17 | return get_generation_error_handler()(counter, name, result.hash_code(), result.string()); 18 | } 19 | 20 | namespace 21 | { 22 | sid::string_info to_string(sid::counter_generator::state s, char *begin, char *end, 23 | std::size_t length) 24 | { 25 | auto cur = end; 26 | std::size_t i = 0; 27 | 28 | do 29 | { 30 | *--cur = '0' + (s % 10); 31 | s /= 10; 32 | ++i; 33 | } while (s != 0u); 34 | 35 | if (i < length) 36 | for (; cur - 1 != begin && i < length; ++i) 37 | *--cur = '0'; 38 | else if (length && i > length) 39 | cur += i - length; 40 | 41 | return sid::string_info(cur, end - cur); 42 | } 43 | } 44 | 45 | sid::string_id sid::counter_generator::operator()() 46 | { 47 | // 4 times sizeof(state) is enough for the integer representation 48 | static FOONATHAN_CONSTEXPR auto max_size = 4 * sizeof(state); 49 | char string[max_size]; 50 | return detail::try_generate("foonathan::string_id::counter_generator", 51 | [&]() 52 | { 53 | return to_string(counter_++, string, string + max_size, length_); 54 | }, prefix_); 55 | } 56 | 57 | void sid::counter_generator::discard(unsigned long long n) FOONATHAN_NOEXCEPT 58 | { 59 | counter_ += n; 60 | } 61 | 62 | namespace 63 | { 64 | static FOONATHAN_CONSTEXPR char table[] 65 | = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxzy0123456789"; 66 | } 67 | 68 | sid::character_table sid::character_table::alnum() FOONATHAN_NOEXCEPT 69 | { 70 | return {table, sizeof(table) - 1}; 71 | } 72 | 73 | sid::character_table sid::character_table::alpha() FOONATHAN_NOEXCEPT 74 | { 75 | return {table, sizeof(table) - 1 - 10}; 76 | } 77 | -------------------------------------------------------------------------------- /generator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #ifndef FOONATHAN_STRING_ID_GENERATOR_HPP_INCLUDED 6 | #define FOONATHAN_STRING_ID_GENERATOR_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | 11 | #include "config.hpp" 12 | #include "string_id.hpp" 13 | 14 | namespace foonathan { namespace string_id 15 | { 16 | namespace detail 17 | { 18 | bool handle_generation_error(std::size_t counter, const char *name, const string_id &result); 19 | 20 | template 21 | string_id try_generate(const char *name, Generator generator, const string_id &prefix) 22 | { 23 | basic_database::insert_status status; 24 | auto result = string_id(prefix, generator(), status); 25 | for (std::size_t counter = 1; 26 | status != basic_database::new_string && 27 | handle_generation_error(counter, name, result); 28 | ++counter) 29 | result = string_id(prefix, generator(), status); 30 | return result; 31 | } 32 | } 33 | 34 | /// \brief A generator that generates string ids with a prefix followed by a number. 35 | /// \detail It can be used by multiple threads at the same time. 36 | class counter_generator 37 | { 38 | public: 39 | /// \brief The type of the internal state, an unsigned integer. 40 | typedef unsigned long long state; 41 | 42 | /// \brief Creates a new generator with given prefix. 43 | /// \arg \c counter is the start value for the counter. 44 | /// \arg \c length is the length of the number appended.If it is \c 0, 45 | /// there are no restrictions. Else it will either prepend zeros to the number 46 | /// or cut the number to \c length digits. 47 | /// \note For implementation reasons, \c length can't be higher than a certain value. 48 | /// If it is, it behaves as if \c length has this certain value. 49 | explicit counter_generator(const string_id &prefix, 50 | state counter = 0, std::size_t length = 0) 51 | : prefix_(prefix), counter_(counter), length_(length) {} 52 | 53 | /// \brief Generates a new \ref string_id. 54 | /// \detail If it was already generated previously, the \ref generator_error_handler will be called in a loop as described there. 55 | string_id operator()(); 56 | 57 | /// \brief Discards a number of states by advancing the counter. 58 | void discard(unsigned long long n) FOONATHAN_NOEXCEPT; 59 | 60 | private: 61 | string_id prefix_; 62 | std::atomic counter_; 63 | std::size_t length_; 64 | }; 65 | 66 | /// \brief Information about the characters used by \ref random_generator. 67 | struct character_table 68 | { 69 | /// \brief A pointer to an array of characters. 70 | const char* characters; 71 | /// \brief The length of it. 72 | std::size_t no_characters; 73 | 74 | /// \brief Creates a new table. 75 | character_table(const char* chars, std::size_t no) FOONATHAN_NOEXCEPT 76 | : characters(chars), no_characters(no) {} 77 | 78 | /// \brief A table with all English letters (both cases) and digits. 79 | static character_table alnum() FOONATHAN_NOEXCEPT; 80 | 81 | /// \brief A table with all English letters (both cases). 82 | static character_table alpha() FOONATHAN_NOEXCEPT; 83 | }; 84 | 85 | /// \brief A generator that generates string ids by appendending random characters to a prefix. 86 | /// \detail This class is thread safe if the random number generator is thread safe. 87 | template 88 | class random_generator 89 | { 90 | public: 91 | /// \brief The state of generator, a random number generator like \c std::mt19937. 92 | typedef RandomNumberGenerator state; 93 | 94 | /// \brief The number of characters appended. 95 | static FOONATHAN_CONSTEXPR_FNC std::size_t length() FOONATHAN_NOEXCEPT 96 | { 97 | return Length; 98 | } 99 | 100 | /// \brief Creates a new generator with given prefix, random number generator and character table. 101 | /// \detail By default is uses the table \ref character_table::alnum(). 102 | explicit random_generator(const string_id &prefix, 103 | state s = state(), 104 | character_table table = character_table::alnum()) 105 | : prefix_(prefix), state_(std::move(s)), 106 | table_(table) {} 107 | 108 | /// \brief Generates a new \ref string_id. 109 | /// \detail If it was already generated previously, the \ref generator_error_handler will be called in a loop as described there. 110 | string_id operator()() 111 | { 112 | std::uniform_int_distribution 113 | dist(0, table_.no_characters - 1); 114 | char random[Length]; 115 | return detail::try_generate("foonathan::string_id::random_generator", 116 | [&]() 117 | { 118 | for (std::size_t i = 0u; i != Length; ++i) 119 | random[i] = table_.characters[dist(state_)]; 120 | return string_info(random, Length); 121 | }, prefix_); 122 | } 123 | 124 | /// \brief Discards a certain number of states, this forwards to the random number generator. 125 | void discard(unsigned long long n) 126 | { 127 | state_.discard(n); 128 | } 129 | 130 | private: 131 | string_id prefix_; 132 | state state_; 133 | character_table table_; 134 | }; 135 | }} // namespace foonathan::string_id 136 | 137 | #endif // FOONATHAN_STRING_ID_GENERATOR_HPP_INCLUDED 138 | -------------------------------------------------------------------------------- /hash.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #ifndef FOONATHAN_STRING_ID_HASH_HPP_INCLUDED 6 | #define FOONATHAN_STRING_ID_HASH_HPP_INCLUDED 7 | 8 | #include 9 | 10 | #include "config.hpp" 11 | 12 | namespace foonathan { namespace string_id 13 | { 14 | /// \brief The type of a hashed string. 15 | /// \detail This is an unsigned integral type. 16 | typedef std::uint64_t hash_type; 17 | 18 | namespace detail 19 | { 20 | FOONATHAN_CONSTEXPR hash_type fnv_basis = 14695981039346656037ull; 21 | FOONATHAN_CONSTEXPR hash_type fnv_prime = 1099511628211ull; 22 | 23 | // FNV-1a 64 bit hash 24 | FOONATHAN_CONSTEXPR_FNC hash_type sid_hash(const char *str, hash_type hash = fnv_basis) 25 | { 26 | return *str ? sid_hash(str + 1, (hash ^ *str) * fnv_prime) : hash; 27 | } 28 | } // namespace detail 29 | }} // foonathan::string_id 30 | 31 | #endif // FOONATHAN_STRING_ID_DETAIL_HASH_HPP_INCLUDED 32 | -------------------------------------------------------------------------------- /string_id.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #include "string_id.hpp" 6 | 7 | #include "error.hpp" 8 | 9 | namespace sid = foonathan::string_id; 10 | 11 | namespace 12 | { 13 | void handle_collision(sid::basic_database &db, sid::hash_type hash, const char *str) 14 | { 15 | auto handler = sid::get_collision_handler(); 16 | auto second = db.lookup(hash); 17 | handler(hash, str, second); 18 | } 19 | } 20 | 21 | sid::string_id::string_id(string_info str, basic_database &db) 22 | { 23 | basic_database::insert_status status; 24 | *this = string_id(str, db, status); 25 | if (!status) 26 | handle_collision(*db_, id_, str.string); 27 | } 28 | 29 | sid::string_id::string_id(string_info str, basic_database &db, 30 | basic_database::insert_status &status) 31 | : id_(detail::sid_hash(str.string)), db_(&db) 32 | { 33 | status = db_->insert(id_, str.string, str.length); 34 | } 35 | 36 | sid::string_id::string_id(const string_id &prefix, string_info str) 37 | { 38 | basic_database::insert_status status; 39 | *this = string_id(prefix, str, status); 40 | if (!status) 41 | handle_collision(*db_, id_, str.string); 42 | } 43 | 44 | sid::string_id::string_id(const string_id &prefix, string_info str, 45 | basic_database::insert_status &status) 46 | : id_(detail::sid_hash(str.string, prefix.hash_code())), db_(prefix.db_) 47 | { 48 | status = db_->insert_prefix(id_, prefix.hash_code(), str.string, str.length); 49 | } 50 | 51 | const char* sid::string_id::string() const FOONATHAN_NOEXCEPT 52 | { 53 | return db_->lookup(id_); 54 | } 55 | 56 | -------------------------------------------------------------------------------- /string_id.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014-2015 Jonathan Müller 2 | // This file is subject to the license terms in the LICENSE file 3 | // found in the top-level directory of this distribution. 4 | 5 | #ifndef FOONATHAN_STRING_ID_HPP_INCLUDED 6 | #define FOONATHAN_STRING_ID_HPP_INCLUDED 7 | 8 | #include 9 | #include 10 | 11 | #include "basic_database.hpp" 12 | #include "config.hpp" 13 | #include "hash.hpp" 14 | 15 | namespace foonathan { namespace string_id 16 | { 17 | /// \brief Information about a string. 18 | /// \detail It is used to reduce the number of constructors of \ref string_id. 19 | struct string_info 20 | { 21 | /// \brief A pointer to a null-terminated string. 22 | const char *string; 23 | /// \brief The length of this string. 24 | std::size_t length; 25 | 26 | /// \brief Creates it from a null-terminated string (implicit conversion). 27 | string_info(const char *str) FOONATHAN_NOEXCEPT 28 | : string(str), length(std::strlen(str)) {} 29 | 30 | /// \brief Creates it from a string with given length. 31 | /// \arg \c str does not need to be null-terminated. 32 | string_info(const char *str, std::size_t length) 33 | : string(str), length(length) {} 34 | }; 35 | 36 | /// \brief The string identifier class. 37 | /// \detail This is a lightweight class to store strings.
38 | /// It only stores a hash of the string allowing fast copying and comparisons. 39 | class string_id 40 | { 41 | public: 42 | //=== constructors ===// 43 | /// \brief Creates a new id by hashing a given string. 44 | /// \detail It will insert the string into the given \ref database which will copy it.
45 | /// If it encounters a collision, the \ref collision_handler will be called. 46 | string_id(string_info str, basic_database &db); 47 | 48 | /// \brief Creates a new id with a given prefix. 49 | /// \detail The new id will be inserted into the same database as the prefix.
50 | //// Otherwise the same as other constructor. 51 | string_id(const string_id &prefix, string_info str); 52 | 53 | /// @{ 54 | /// \brief Sames as other constructor versions but instead of calling the \ref collision_handler, 55 | /// they set the output parameter to the appropriate status. 56 | /// \detail This also allows information whether or not the string was already stored inside the database. 57 | string_id(string_info str, basic_database &db, 58 | basic_database::insert_status &status); 59 | 60 | string_id(const string_id &prefix, string_info str, 61 | basic_database::insert_status &status); 62 | /// @} 63 | 64 | //=== accessors ===// 65 | /// \brief Returns the hashed value of the string. 66 | hash_type hash_code() const FOONATHAN_NOEXCEPT 67 | { 68 | return id_; 69 | } 70 | 71 | /// \brief Returns a reference to the database. 72 | basic_database& database() const FOONATHAN_NOEXCEPT 73 | { 74 | return *db_; 75 | } 76 | 77 | /// \brief Returns the string value itself. 78 | /// \detail This calls the \c lookup function on the database. 79 | const char* string() const FOONATHAN_NOEXCEPT; 80 | 81 | //=== comparision ===// 82 | /// @{ 83 | /// \brief Compares string ids with another or hashed values. 84 | /// \detail Two string ids are equal if they are from the same database and they have the same value.
85 | /// A hashed value is equal to a string id if it is the same value. 86 | friend bool operator==(string_id a, string_id b) FOONATHAN_NOEXCEPT 87 | { 88 | return a.db_ == b.db_ && a.id_ == b.id_; 89 | } 90 | 91 | friend bool operator==(hash_type a, const string_id &b) FOONATHAN_NOEXCEPT 92 | { 93 | return a == b.id_; 94 | } 95 | 96 | friend bool operator==(const string_id &a, hash_type b) FOONATHAN_NOEXCEPT 97 | { 98 | return a.id_ == b; 99 | } 100 | 101 | friend bool operator!=(const string_id &a, const string_id &b) FOONATHAN_NOEXCEPT 102 | { 103 | return !(a == b); 104 | } 105 | 106 | friend bool operator!=(hash_type a, const string_id &b) FOONATHAN_NOEXCEPT 107 | { 108 | return !(a == b); 109 | } 110 | 111 | friend bool operator!=(const string_id &a, hash_type b) FOONATHAN_NOEXCEPT 112 | { 113 | return !(a == b); 114 | } 115 | /// @} 116 | 117 | private: 118 | hash_type id_; 119 | basic_database *db_; 120 | }; 121 | 122 | namespace literals 123 | { 124 | /// \brief Same as the literal version, additional replacement if not supported. 125 | FOONATHAN_CONSTEXPR_FNC hash_type id(const char *str) 126 | { 127 | return detail::sid_hash(str); 128 | } 129 | 130 | /// \brief A useful literal to hash a string. 131 | /// \detail Since this function does not check for collisions only use it to compare a \ref string_id.
132 | /// It is also useful in places where a compile-time constant is needed. 133 | #if FOONATHAN_STRING_ID_HAS_LITERAL 134 | FOONATHAN_CONSTEXPR_FNC hash_type operator""_id(const char *str, std::size_t) 135 | { 136 | return detail::sid_hash(str); 137 | } 138 | #endif 139 | } // namespace literals 140 | }} // namespace foonathan::string_id 141 | 142 | namespace std 143 | { 144 | /// \brief \c std::hash support for \ref string_id. 145 | template <> 146 | struct hash 147 | { 148 | typedef foonathan::string_id::string_id argument_type; 149 | typedef size_t result_type; 150 | 151 | result_type operator()(const argument_type &arg) const FOONATHAN_NOEXCEPT 152 | { 153 | return static_cast(arg.hash_code()); 154 | } 155 | }; 156 | } // namspace std 157 | 158 | #endif // FOONATHAN_STRING_ID_HPP_INCLUDED 159 | --------------------------------------------------------------------------------