├── .gitignore ├── LICENSE ├── README.md ├── build └── CMakeLists.txt ├── include └── stateful_pointer │ ├── string.hpp │ └── tagged_ptr.hpp └── test ├── bm_json2md.py ├── bm_tagged_ptr.cpp ├── test_string.cpp └── test_tagged_ptr.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stateful Pointer Library 2 | 3 | Sometimes space is tight! What if you could squeeze *extra state* into a pointer *at (almost) no additional cost*? 4 | 5 | A pointer occupies 32 or 64 bit, which is 4 or 8 bytes just to remember a memory address. On common hardware-platforms, some bits of the address are not used, because the computer allocates *aligned* memory addresses which are multiples of an alignment value. The *Stateful Pointer Library* is a C++11 header-only library which provides safe access to those bits. It contains a smart pointer that mimics `std::unique_ptr`, but allows you to use up to 24 bits to store extra state inside the pointer in a safe way. These freely useable bits are encoded inside the pointer itself and occupy *no extra space*. 6 | 7 | The library uses [Boost.Align](http://www.boost.org/doc/libs/1_65_1/doc/html/align.html) to allocate aligned memory. On most platforms (Windows, MacOS, Linux, Android, ...), special system calls are used to get aligned memory at no additional cost. On other platforms, extra memory is allocated to guarantee the alignment of the pointer. The amount grows with the number of bits in the pointer that are used to carry extra state. In either case, the pointers of the *Stateful Pointer Library* are guaranteed to have the same size as a normal pointer. 8 | 9 | **Platform dependence**: The library relies on two platform-dependent aspects of memory handling. 10 | 11 | * Memory addresses map trivially to consecutive integral numbers. 12 | * The address of aligned memory, in its representation as an integral number, is an exact multiple of the alignment value. 13 | 14 | The C++ standard not does guarantee these properties, as explained on [StackOverflow](https://stackoverflow.com/questions/34737737/relation-between-numeric-representation-of-memory-address-and-alignment). Nevertheless, common platforms in use today seem to support this simple memory addressing scheme. There is no guarantee, of course, that future platforms will do the same, so use this library with caution. Many thanks go to [the knowledgable Redditors](https://redd.it/73rr47) who pointed all this out. 15 | 16 | **Caveat**: The library uses custom memory allocation to do its magic, so it does *not* work with classes/environments that also customize heap allocation. 17 | 18 | Code is released under the **Boost Software License v1.0** (see LICENSE file). 19 | 20 | ## Tagged pointer 21 | 22 | Like `std::unique_ptr` in interface and behavior, but encodes `N` extra bits of information inside the pointer, using no additional space. 23 | 24 | ```c++ 25 | #include "stateful_pointer/tagged_ptr.hpp" 26 | #include "boost/utility/binary.hpp" // macro to make binary literals 27 | #include 28 | #include 29 | 30 | using namespace stateful_pointer; 31 | 32 | // class to be allocated on the heap 33 | struct A { 34 | int a; 35 | A(int x) : a(x) {} 36 | }; 37 | 38 | int main() { 39 | // tagged_ptr has the same size as a normal pointer 40 | assert(sizeof(tagged_ptr) == sizeof(void*)); 41 | 42 | // make tagged_ptr to an instance of A with 4 bits of extra state 43 | auto p = make_tagged(3); // 3 is passed to the ctor of A 44 | 45 | // set the 4 bits to some values 46 | p.bits(BOOST_BINARY( 1010 )); // that's 10 in decimal 47 | 48 | std::cout << "a = " << p->a << ", bits = " << p.bits() << std::endl; 49 | 50 | // prints: "a = 3, bits = 10" 51 | } 52 | ``` 53 | 54 | ## String 55 | 56 | The World's most compact STL-compatible string with *small string optimization*. Has the size of a mere pointer and yet stores up to 7 characters (on a 64-bit system) without allocating extra memory on the heap. 57 | 58 | ```c++ 59 | #include "stateful_pointer/string.hpp" 60 | #include 61 | #include 62 | 63 | using namespace stateful_pointer; 64 | 65 | int main() { 66 | // string has the same size as a normal pointer 67 | assert(sizeof(string) == sizeof(void*)); 68 | 69 | string s("foo bar"); // small string optimisation: no heap allocation 70 | std::cout << s << std::endl; 71 | 72 | // prints: "foo bar" 73 | } 74 | ``` 75 | 76 | This one is still in development, a lot of the standard interface is still missing. 77 | 78 | ## Performance 79 | 80 | ### `tagged_ptr` vs `std::unique_ptr` 81 | 82 | In optimized builds, the performance is similar. Most importantly, access is as fast. Pointer creation is at most 10 % slower and becomes negligible compared to the allocation and initialization cost of larger pointees. 83 | 84 | |Benchmark |CPU [ns]| 85 | |:-------------------------------------------|-------:| 86 | |`unique_ptr_creation` | 29| 87 | |`tagged_ptr_creation` | 32| 88 | |`unique_ptr_creation>`| 70| 89 | |`tagged_ptr_creation>`| 72| 90 | |`unique_ptr_access` | 2| 91 | |`tagged_ptr_access` | 2| 92 | |`unique_ptr_access>` | 2| 93 | |`tagged_ptr_access>` | 2| 94 | 95 | ([Google benchmark library](https://github.com/google/benchmark) run on 4x3GHz CPUs, compiled with -O3) 96 | -------------------------------------------------------------------------------- /build/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.2) 2 | 3 | project(stateful_pointer CXX) 4 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) 5 | 6 | set(CMAKE_CXX_STANDARD 11) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | 9 | find_package(Boost 1.61 REQUIRED) 10 | find_path(BENCHMARK_INCLUDE_DIRS benchmark/Benchmark.h) 11 | find_library(BENCHMARK_LIBRARY benchmark) 12 | 13 | include_directories(../include ${Boost_INCLUDE_DIRS}) 14 | 15 | # tests 16 | enable_testing() 17 | 18 | file(GLOB TEST_SOURCES ../test/test_*.cpp ../examples/*.cpp) 19 | foreach(SRC IN ITEMS ${TEST_SOURCES}) 20 | if(SRC MATCHES "/([_a-zA-Z0-9]+)\\.cpp") 21 | add_executable(${CMAKE_MATCH_1} ${SRC}) 22 | target_compile_options(${CMAKE_MATCH_1} PUBLIC $<$:-O0 -g>) 23 | add_test(${CMAKE_MATCH_1} ${CMAKE_MATCH_1}) 24 | endif() 25 | endforeach() 26 | 27 | # benchmarks 28 | if (BENCHMARK_INCLUDE_DIRS AND BENCHMARK_LIBRARY) 29 | include_directories(${BENCHMARK_INCLUDE_DIRS}) 30 | file(GLOB TEST_SOURCES ../test/bm_*.cpp) 31 | foreach(SRC IN ITEMS ${TEST_SOURCES}) 32 | if(SRC MATCHES "/([_a-zA-Z0-9]+)\\.cpp") 33 | foreach(L 0 3) 34 | add_executable(${CMAKE_MATCH_1}_o${L} ${SRC}) 35 | target_compile_options(${CMAKE_MATCH_1}_o${L} PUBLIC $<$:-O${L}>) 36 | target_link_libraries(${CMAKE_MATCH_1}_o${L} ${BENCHMARK_LIBRARY} -lpthread) 37 | endforeach() 38 | endif() 39 | endforeach() 40 | else() 41 | message(STATUS "Google Benchmark not found, benchmarks are not build") 42 | endif() 43 | 44 | # for clang format and clang tidy 45 | file(GLOB_RECURSE 46 | ALL_SOURCE_FILES 47 | LIST_DIRECTORIES false 48 | ../test/*.cpp ../include/*.hpp 49 | ) 50 | 51 | add_custom_target(clf 52 | COMMAND clang-format 53 | -i 54 | ${ALL_SOURCE_FILES} 55 | ) 56 | 57 | get_property(INCLUDE_DIRS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) 58 | set(TIDY_INCLUDE) 59 | foreach(x ${INCLUDE_DIRS}) 60 | LIST(APPEND TIDY_INCLUDE -I${x}) 61 | endforeach() 62 | add_custom_target( 63 | clang-tidy 64 | COMMAND clang-tidy 65 | ${ALL_SOURCE_FILES} 66 | -checks=*,-*alpha* 67 | -- 68 | -std=c++11 69 | ${TIDY_INCLUDE} 70 | ) 71 | -------------------------------------------------------------------------------- /include/stateful_pointer/string.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STATEFUL_POINTER_STRING_HPP 2 | #define STATEFUL_POINTER_STRING_HPP 3 | 4 | #include "boost/cstdint.hpp" 5 | #include "stateful_pointer/tagged_ptr.hpp" 6 | // #include "boost/assert.hpp" 7 | // #include "boost/type_traits.hpp" 8 | #include "boost/utility/binary.hpp" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace stateful_pointer { 15 | 16 | namespace detail { 17 | template ()), 18 | std::end(std::declval()))> 19 | struct is_sequence {}; 20 | } // namespace detail 21 | 22 | template class basic_string { 23 | using tagged_ptr_t = tagged_ptr; 24 | using bits_type = typename tagged_ptr_t::bits_type; 25 | static constexpr unsigned N = sizeof(void *) / sizeof(TChar); 26 | 27 | public: 28 | using pos_type = std::size_t; 29 | using value_type = typename tagged_ptr_t::element_type; 30 | using pointer = value_type *; 31 | using const_pointer = value_type const *; 32 | using reference = value_type &; 33 | using const_reference = value_type const &; 34 | using iterator = pointer; 35 | using const_iterator = const_pointer; 36 | 37 | constexpr basic_string() noexcept {} 38 | 39 | basic_string(pos_type count, value_type ch) { 40 | if (N > 0 && count < N) { // small string optimisation 41 | auto cp = reinterpret_cast(&value) + 1; 42 | std::fill_n(cp, count, ch); 43 | reinterpret_cast(value) |= count << 1; 44 | // value.bit(0) remains false 45 | } else { // normal use 46 | value = make_tagged(count + 1); 47 | value.bit(0, true); 48 | auto cp = value.get(); 49 | std::fill_n(cp, count, ch); 50 | *(cp + count) = 0; 51 | } 52 | } 53 | 54 | basic_string(const basic_string &other, pos_type pos, pos_type count) { 55 | auto first = other.begin() + pos; 56 | assign_impl(first, std::min(other.end(), first + count)); 57 | } 58 | 59 | basic_string(const basic_string &other, pos_type pos) { 60 | assign_impl(other.begin() + pos, other.end()); 61 | } 62 | 63 | basic_string(const value_type *s, pos_type count) { 64 | if (count > 0 && !s) 65 | throw std::logic_error("null in constructor not valid"); 66 | assign_impl(s, s + count); 67 | } 68 | 69 | basic_string(const value_type *s) { 70 | if (!s) 71 | throw std::logic_error("null in constructor not valid"); 72 | auto end = s; 73 | while (*end++) 74 | ; 75 | assign_impl(s, --end); 76 | } 77 | 78 | template basic_string(InputIt first, InputIt last) { 79 | assign_impl(first, last); 80 | } 81 | 82 | ~basic_string() { 83 | if (!value.bit(0)) { // we are in small string optimisation mode 84 | // prevent tagged_ptr destructor from running 85 | reinterpret_cast(value) = 0; 86 | } 87 | } 88 | 89 | const_iterator begin() const noexcept { 90 | return begin_impl(value); 91 | } 92 | 93 | const_iterator end() const noexcept { 94 | return end_impl(value); 95 | } 96 | 97 | iterator begin() noexcept { return begin_impl(value); } 98 | 99 | iterator end() noexcept { return end_impl(value); } 100 | 101 | bool empty() const noexcept { 102 | return reinterpret_cast(value) == 0 || size() == 0; 103 | } 104 | 105 | pos_type size() const noexcept { 106 | if (value.bit(0)) { 107 | auto size = value.size(); 108 | return size ? size - 1 : 0; 109 | } 110 | return (reinterpret_cast(value) & size_mask) >> 1; 111 | } 112 | 113 | pos_type length() const noexcept { return size(); } 114 | 115 | bool operator==(const value_type *s) const { 116 | auto send = s; 117 | while (*send++) 118 | ; 119 | --send; 120 | auto first = begin(); 121 | auto last = end(); 122 | return (std::distance(first, last) == std::distance(s, send)) && 123 | std::equal(first, last, s); 124 | } 125 | 126 | template > 127 | bool operator==(const Container &c) const { 128 | auto cfirst = std::begin(c); 129 | auto cend = std::end(c); 130 | auto first = begin(); 131 | auto last = end(); 132 | return (std::distance(first, last) == std::distance(cfirst, cend)) && 133 | std::equal(first, last, cfirst); 134 | } 135 | 136 | const_reference operator[](pos_type i) const { return *(begin() + i); } 137 | 138 | reference operator[](pos_type i) { return *(begin() + i); } 139 | 140 | private: 141 | static constexpr bits_type size_mask = BOOST_BINARY(11111110); 142 | 143 | template void assign_impl(InputIt first, InputIt last) { 144 | const auto n = std::distance(first, last); 145 | 146 | if (value.bit(0) && n <= size()) { 147 | // normal pointer, reuse allocated memory 148 | auto cp = value.get(); 149 | std::copy(first, last, cp); 150 | *(cp + n) = 0; 151 | return; 152 | } 153 | 154 | if (!value.bit(0)) { 155 | // small string optimisation: characters stored inside pointer memory 156 | reinterpret_cast(value) = 0; // wipe memory 157 | if (N > 0 && n < N) { 158 | // small string optimisation remains possible 159 | auto cp = reinterpret_cast(&value) + 1; 160 | std::copy(first, last, cp); 161 | reinterpret_cast(value) |= n << 1; 162 | // value.bit(0) remains false 163 | return; 164 | } 165 | } 166 | 167 | // allocate new memory 168 | // value.bit(0) == true marks normal pointer use 169 | value = make_tagged(n + 1); 170 | value.bit(0, true); 171 | auto cp = value.get(); 172 | std::copy(first, last, cp); 173 | *(cp + n) = 0; 174 | } 175 | 176 | template static It begin_impl(T &t) noexcept { 177 | return t.bit(0) ? t.get() : reinterpret_cast(&t) + 1; 178 | } 179 | 180 | template static It end_impl(T &t) noexcept { 181 | if (t.bit(0)) { 182 | const auto n = t.size(); 183 | return t.get() + (n ? n - 1 : 0); 184 | } else { 185 | const auto n = (reinterpret_cast(t) & size_mask) >> 1; 186 | return reinterpret_cast(&t) + 1 + n; 187 | } 188 | } 189 | 190 | friend std::ostream &operator<<(std::ostream &os, const basic_string &s) { 191 | for (const auto &ch : s) 192 | os << ch; 193 | return os; 194 | } 195 | 196 | tagged_ptr_t value; 197 | }; 198 | 199 | using string = basic_string; 200 | using wstring = basic_string; 201 | } // namespace stateful_pointer 202 | 203 | #endif 204 | -------------------------------------------------------------------------------- /include/stateful_pointer/tagged_ptr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STATEFUL_POINTER_TAGGED_PTR_HPP 2 | #define STATEFUL_POINTER_TAGGED_PTR_HPP 3 | 4 | #include "boost/align/aligned_alloc.hpp" 5 | #include "boost/align/alignment_of.hpp" 6 | #include "boost/assert.hpp" 7 | #include "boost/cstdint.hpp" 8 | #include "boost/type_traits.hpp" 9 | #include 10 | 11 | namespace stateful_pointer { 12 | 13 | namespace detail { 14 | constexpr ::boost::uintptr_t max(::boost::uintptr_t a, ::boost::uintptr_t b) { 15 | return a > b ? a : b; 16 | } 17 | constexpr unsigned pow2(unsigned n) noexcept { 18 | return n > 0 ? 2 * pow2(n - 1) : 1; 19 | } 20 | constexpr ::boost::uintptr_t make_ptr_mask(unsigned n) noexcept { 21 | return ~::boost::uintptr_t(0) << n; 22 | } 23 | template struct make_dispatch; 24 | } // namespace detail 25 | 26 | template class tagged_ptr { 27 | public: 28 | using bits_type = ::boost::uintptr_t; 29 | using pos_type = std::size_t; // only for array version 30 | using element_type = typename ::boost::remove_extent::type; 31 | using pointer = element_type *; 32 | using reference = element_type &; 33 | 34 | constexpr tagged_ptr() noexcept : value(0) {} 35 | 36 | // tagged_ptr models exclusive ownership, no copies allowed 37 | tagged_ptr(const tagged_ptr &) = delete; 38 | tagged_ptr &operator=(const tagged_ptr &) = delete; 39 | 40 | tagged_ptr(tagged_ptr &&other) noexcept : value(other.value) { 41 | other.value = 0; 42 | } 43 | 44 | tagged_ptr &operator=(tagged_ptr &&other) noexcept { 45 | if (this != &other) { 46 | value = other.value; 47 | other.value = 0; 48 | } 49 | return *this; 50 | } 51 | 52 | /// move constructor that allows conversion between base and derived 53 | template ::value) && 55 | ::boost::is_convertible::value>::type> 56 | tagged_ptr(tagged_ptr &&other) noexcept : value(other.value) { 57 | other.value = 0; 58 | } 59 | 60 | /// move assignment that allows conversion between base and derived 61 | template ::value) && 63 | ::boost::is_convertible::value>::type> 64 | tagged_ptr &operator=(tagged_ptr &&other) noexcept { 65 | if (this != &other) { 66 | value = other.value; 67 | other.value = 0; 68 | } 69 | return *this; 70 | } 71 | 72 | ~tagged_ptr() { 73 | auto tp = get(); 74 | if (tp) 75 | delete_dispatch::doit(tp); 76 | } 77 | 78 | /// get tag bits as integral type 79 | bits_type bits() const noexcept { return value & tag_mask; } 80 | 81 | /// set tag bits via integral type, ptr bits are not overridden 82 | void bits(bits_type b) noexcept { 83 | value &= ptr_mask; // clear old bits 84 | value |= (b & tag_mask); // set new bits 85 | } 86 | 87 | /// get bit at position pos 88 | bool bit(unsigned pos) const noexcept { return value & (1 << pos); } 89 | 90 | /// set bit at position pos to value b 91 | void bit(unsigned pos, bool b) noexcept { 92 | BOOST_ASSERT(pos < Nbits); 93 | if (b) 94 | value |= (1 << pos); 95 | else 96 | value &= ~(1 << pos); 97 | } 98 | 99 | /// get raw pointer in the fast way possible (no checks for nullness) 100 | pointer get() const noexcept { return extract_ptr(value); } 101 | 102 | /// release ownership of raw pointer, bits remain intact 103 | pointer release() noexcept { 104 | auto tmp = get(); 105 | value &= ~ptr_mask; // nullify pointer bits 106 | return tmp; 107 | } 108 | 109 | /// reset pointer and bits to p 110 | void reset(tagged_ptr p = tagged_ptr()) noexcept { p.swap(*this); } 111 | 112 | /// swap pointer and bits with other 113 | void swap(tagged_ptr &other) noexcept { std::swap(value, other.value); } 114 | 115 | /// dereference operator, throws error in debug mode if pointer is null 116 | auto operator*() const -> reference { 117 | const auto p = get(); 118 | BOOST_ASSERT(p != nullptr); 119 | return *p; 120 | } 121 | 122 | /// array element access (only for array version), throws error in debug mode 123 | /// if bounds are violated 124 | template >::type> 126 | reference operator[](pos_type i) const { 127 | auto p = get(); 128 | BOOST_ASSERT(i < size()); 129 | return *(p + i); 130 | } 131 | 132 | /// array size (only for array version) 133 | template >::type> 135 | pos_type size() const noexcept { 136 | return size_dispatch::doit(get()); 137 | } 138 | 139 | /// member access operator 140 | pointer operator->() const noexcept { return get(); } 141 | 142 | explicit operator bool() const noexcept { return static_cast(get()); } 143 | 144 | bool operator!() const noexcept { return get() == 0; } 145 | 146 | private: 147 | static constexpr bits_type ptr_mask = detail::make_ptr_mask(Nbits); 148 | static constexpr bits_type tag_mask = ~ptr_mask; 149 | 150 | static pointer extract_ptr(bits_type v) noexcept { 151 | return reinterpret_cast(v & ptr_mask); 152 | } 153 | 154 | static pointer *array_end_p(pointer p) noexcept { 155 | return reinterpret_cast(reinterpret_cast(p) - 156 | sizeof(pointer)); 157 | }; 158 | 159 | template struct size_dispatch { 160 | static pos_type doit(pointer p) noexcept { return *array_end_p(p) - p; } 161 | }; 162 | 163 | template struct size_dispatch { 164 | static pos_type doit(pointer) noexcept { return N; } 165 | }; 166 | 167 | template struct delete_dispatch { 168 | static void doit(pointer p) { 169 | // automatically skipped if T has trivial destructor 170 | p->~element_type(); 171 | ::boost::alignment::aligned_free(p); 172 | } 173 | }; 174 | 175 | template struct delete_dispatch { 176 | static void doit(pointer iter) { 177 | auto end_p = array_end_p(iter); 178 | auto p = reinterpret_cast(end_p); 179 | if (!::boost::has_trivial_destructor::value) { 180 | auto end = *end_p; 181 | while (iter != end) 182 | (iter++)->~element_type(); 183 | } 184 | ::boost::alignment::aligned_free(p); 185 | } 186 | }; 187 | 188 | template struct delete_dispatch { 189 | static void doit(pointer p) { 190 | if (!::boost::has_trivial_destructor::value) { 191 | auto iter = p; 192 | for (decltype(N) i = 0; i < N; ++i) 193 | (iter++)->~element_type(); 194 | } 195 | ::boost::alignment::aligned_free(p); 196 | } 197 | }; 198 | 199 | friend bool operator==(const tagged_ptr &a, const tagged_ptr &b) noexcept { 200 | return a.value == b.value; 201 | } 202 | 203 | friend bool operator!=(const tagged_ptr &a, const tagged_ptr &b) noexcept { 204 | return a.value != b.value; 205 | } 206 | 207 | friend bool operator<(const tagged_ptr &a, const tagged_ptr &b) noexcept { 208 | return a.value < b.value; 209 | } 210 | 211 | friend bool operator<=(const tagged_ptr &a, const tagged_ptr &b) noexcept { 212 | return a.value <= b.value; 213 | } 214 | 215 | friend bool operator>(const tagged_ptr &a, const tagged_ptr &b) noexcept { 216 | return a.value > b.value; 217 | } 218 | 219 | friend bool operator>=(const tagged_ptr &a, const tagged_ptr &b) noexcept { 220 | return a.value >= b.value; 221 | } 222 | 223 | friend void swap(tagged_ptr &a, tagged_ptr &b) noexcept { a.swap(b); } 224 | 225 | template friend class tagged_ptr; 226 | 227 | template friend struct detail::make_dispatch; 228 | 229 | bits_type value; 230 | }; 231 | 232 | namespace detail { 233 | template struct make_dispatch { 234 | template 235 | static tagged_ptr doit(Args &&... args) { 236 | tagged_ptr p; 237 | auto address = ::boost::alignment::aligned_alloc( 238 | detail::max(detail::pow2(Nbits), 239 | ::boost::alignment::alignment_of::value), 240 | sizeof(T)); 241 | p.value = reinterpret_cast(address); 242 | new (address) T(std::forward(args)...); 243 | return p; 244 | } 245 | }; 246 | 247 | template 248 | struct make_dispatch { 249 | template 250 | static tagged_ptr doit(Args &&... args) { 251 | tagged_ptr p; 252 | auto address = ::boost::alignment::aligned_alloc( 253 | detail::max(detail::pow2(Nbits), 254 | ::boost::alignment::alignment_of::value), 255 | N * sizeof(T)); 256 | p.value = reinterpret_cast(address); 257 | auto iter = reinterpret_cast(address); 258 | for (decltype(N) i = 0; i < N; ++i) { 259 | new (iter++) T(std::forward(args)...); 260 | } 261 | return p; 262 | } 263 | }; 264 | 265 | template struct make_dispatch { 266 | template 267 | static tagged_ptr doit(std::size_t size, Args &&... args) { 268 | tagged_ptr p; 269 | auto address = reinterpret_cast(::boost::alignment::aligned_alloc( 270 | detail::max(detail::pow2(Nbits), 271 | ::boost::alignment::alignment_of::value), 272 | sizeof(T *) + size * sizeof(T))); 273 | auto iter = reinterpret_cast(address + sizeof(T *)); 274 | const auto end = iter + size; 275 | *reinterpret_cast(address) = end; 276 | p.value = reinterpret_cast(iter); 277 | while (iter != end) 278 | new (iter++) T(std::forward(args)...); 279 | return p; 280 | } 281 | }; 282 | } // namespace detail 283 | 284 | template 285 | tagged_ptr make_tagged(Args &&... args) { 286 | return detail::make_dispatch::doit(std::forward(args)...); 287 | } 288 | 289 | } // namespace stateful_pointer 290 | 291 | #endif 292 | -------------------------------------------------------------------------------- /test/bm_json2md.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import json 4 | import sys 5 | 6 | parser = argparse.ArgumentParser() 7 | parser.add_argument("header", nargs="+") 8 | 9 | bm = json.load(sys.stdin) 10 | 11 | args = parser.parse_args() 12 | 13 | columns = [] 14 | 15 | names = ["Benchmark"] 16 | for d in bm["benchmarks"]: 17 | names.append("`" + d["name"] + "`") 18 | 19 | columns.append(names) 20 | 21 | for arg in args.header: 22 | print arg 23 | if arg == "c": 24 | col = ["CPU [%s]" % bm["benchmarks"][0]["time_unit"]] 25 | for d in bm["benchmarks"]: 26 | col.append("%.0f" % d["cpu_time"]) 27 | elif arg == "r": 28 | col = ["Real [%s]" % bm["benchmarks"][0]["time_unit"]] 29 | for d in bm["benchmarks"]: 30 | col.append("%.0f" % d["real_time"]) 31 | elif arg == "i": 32 | col = ["Iterations"] 33 | for d in bm["benchmarks"]: 34 | col.append("%i" % d["iterations"]) 35 | columns.append(col) 36 | 37 | widths = [max([len(x) for x in col]) for col in columns] 38 | 39 | for i,n in enumerate(names): 40 | names[i] = n + " "*(widths[0]-len(n)) 41 | 42 | for irow in range(len(names)): 43 | line = "|" + "|".join([("%%%is" % widths[icol]) % col[irow] for icol, col in enumerate(columns)]) + "|" 44 | sys.stdout.write(line + "\n") 45 | if irow == 0: 46 | line = "|:" + "-"*(widths[0]-1) + "|" + ":|".join(["-"*(w-1) for w in widths[1:]]) + ":|" 47 | sys.stdout.write(line + "\n") 48 | -------------------------------------------------------------------------------- /test/bm_tagged_ptr.cpp: -------------------------------------------------------------------------------- 1 | #include "array" 2 | #include "benchmark/benchmark.h" 3 | #include "memory" 4 | #include "stateful_pointer/tagged_ptr.hpp" 5 | 6 | namespace sp = stateful_pointer; 7 | 8 | template static void unique_ptr_creation(benchmark::State &state) { 9 | while (state.KeepRunning()) { 10 | benchmark::DoNotOptimize(std::unique_ptr(new T())); 11 | } 12 | } 13 | 14 | template static void tagged_ptr_creation(benchmark::State &state) { 15 | while (state.KeepRunning()) { 16 | benchmark::DoNotOptimize(sp::make_tagged()); 17 | } 18 | } 19 | 20 | template static void unique_ptr_access(benchmark::State &state) { 21 | auto p = std::unique_ptr(new T()); 22 | while (state.KeepRunning()) { 23 | benchmark::DoNotOptimize(p.get()); 24 | } 25 | } 26 | 27 | template static void tagged_ptr_access(benchmark::State &state) { 28 | auto p = sp::make_tagged(); 29 | while (state.KeepRunning()) { 30 | benchmark::DoNotOptimize(p.get()); 31 | } 32 | } 33 | 34 | BENCHMARK_TEMPLATE(unique_ptr_creation, char); 35 | BENCHMARK_TEMPLATE(tagged_ptr_creation, char); 36 | BENCHMARK_TEMPLATE(unique_ptr_creation, std::array); 37 | BENCHMARK_TEMPLATE(tagged_ptr_creation, std::array); 38 | BENCHMARK_TEMPLATE(unique_ptr_access, char); 39 | BENCHMARK_TEMPLATE(tagged_ptr_access, char); 40 | BENCHMARK_TEMPLATE(unique_ptr_access, std::array); 41 | BENCHMARK_TEMPLATE(tagged_ptr_access, std::array); 42 | 43 | BENCHMARK_MAIN(); 44 | -------------------------------------------------------------------------------- /test/test_string.cpp: -------------------------------------------------------------------------------- 1 | #include "algorithm" 2 | #include "boost/align/aligned_alloc.hpp" 3 | #include "boost/core/lightweight_test.hpp" 4 | #include "sstream" 5 | #include "stdexcept" 6 | 7 | static auto alloc_count = 0u; 8 | namespace boost { 9 | namespace alignment { 10 | void *custom_aligned_alloc(std::size_t alignment, std::size_t size) noexcept { 11 | ++alloc_count; 12 | return aligned_alloc(alignment, size); 13 | } 14 | } // namespace alignment 15 | } // namespace boost 16 | #define aligned_alloc(alignment, size) custom_aligned_alloc(alignment, size) 17 | #include "stateful_pointer/string.hpp" 18 | 19 | using namespace stateful_pointer; 20 | 21 | int main() { 22 | 23 | alloc_count = 0; 24 | { // ctors 25 | string s1; 26 | BOOST_TEST_EQ(s1.size(), 0u); 27 | BOOST_TEST(s1.empty()); 28 | 29 | BOOST_TEST_THROWS(string(nullptr), std::logic_error); 30 | 31 | string s2(""); 32 | BOOST_TEST(s2.empty()); 33 | BOOST_TEST_EQ(s2.size(), 0); 34 | 35 | string s3("abc"); // uses small string optimisation 36 | BOOST_TEST(!s3.empty()); 37 | BOOST_TEST_EQ(s3.size(), 3); 38 | BOOST_TEST_EQ(std::distance(s3.begin(), s3.end()), 3u); 39 | BOOST_TEST_EQ(s3[0], 'a'); 40 | BOOST_TEST_EQ(s3[1], 'b'); 41 | BOOST_TEST_EQ(s3[2], 'c'); 42 | BOOST_TEST(s3 == "abc"); 43 | BOOST_TEST_EQ(alloc_count, 0); 44 | 45 | string s3a("abcdefghijklmnopqrstuvwxyz"); // uses normal allocation 46 | BOOST_TEST(!s3a.empty()); 47 | BOOST_TEST_EQ(s3a.size(), 26); 48 | BOOST_TEST_EQ(s3a[0], 'a'); 49 | BOOST_TEST_EQ(s3a[25], 'z'); 50 | BOOST_TEST(s3a == "abcdefghijklmnopqrstuvwxyz"); 51 | BOOST_TEST_EQ(alloc_count, 1); 52 | 53 | string s4(s3a, 24); 54 | BOOST_TEST(!s4.empty()); 55 | BOOST_TEST_EQ(s4.size(), 2); 56 | BOOST_TEST(s4 == "yz"); 57 | BOOST_TEST_EQ(alloc_count, 1); 58 | 59 | string s5(s3a, 1, 20); 60 | BOOST_TEST(!s5.empty()); 61 | BOOST_TEST_EQ(s5.size(), 20); 62 | BOOST_TEST(s5 == "bcdefghijklmnopqrstu"); 63 | BOOST_TEST_EQ(alloc_count, 2); 64 | 65 | string s6(7, 'a'); // uses small string opt. 66 | BOOST_TEST(!s6.empty()); 67 | BOOST_TEST_EQ(s6.size(), 7); 68 | BOOST_TEST(s6 == "aaaaaaa"); 69 | BOOST_TEST_EQ(alloc_count, (sizeof(void *) == 8 ? 2 : 3)); 70 | } 71 | 72 | { // ostream operator 73 | std::ostringstream os1; 74 | string s1("abc"); 75 | os1 << s1; 76 | BOOST_TEST_EQ(s1, os1.str()); 77 | 78 | std::ostringstream os2; 79 | string s2("abcdefghijklmnopqrstuvwxyz"); 80 | os2 << s2; 81 | BOOST_TEST_EQ(s2, os2.str()); 82 | } 83 | 84 | return boost::report_errors(); 85 | } 86 | -------------------------------------------------------------------------------- /test/test_tagged_ptr.cpp: -------------------------------------------------------------------------------- 1 | #include "boost/core/lightweight_test.hpp" 2 | #include "boost/utility/binary.hpp" 3 | #include "stateful_pointer/tagged_ptr.hpp" 4 | 5 | int main() { 6 | using namespace stateful_pointer; 7 | 8 | { // check that ptr_mask is correctly made 9 | BOOST_TEST_EQ(detail::make_ptr_mask(0), ~0); 10 | BOOST_TEST_EQ(detail::make_ptr_mask(1), ~1); 11 | BOOST_TEST_EQ(detail::make_ptr_mask(2), ~(1 | 2)); 12 | BOOST_TEST_EQ(detail::make_ptr_mask(3), ~(1 | 2 | 4)); 13 | } 14 | 15 | static unsigned destructor_count_test_type = 0; 16 | struct test_type { 17 | int a; 18 | char b; 19 | test_type() : a(0), b(0) {} 20 | test_type(int x, char y) : a(x), b(y) {} 21 | ~test_type() { ++destructor_count_test_type; } 22 | }; 23 | 24 | // check that tagged_ptr has the same size as void* 25 | BOOST_TEST_EQ(sizeof(tagged_ptr), sizeof(void *)); 26 | 27 | destructor_count_test_type = 0; 28 | { // basic usage 29 | auto p = make_tagged(2, 3); 30 | 31 | BOOST_TEST(!!p); 32 | BOOST_TEST_EQ(static_cast(p), true); 33 | BOOST_TEST_EQ(p.bits(), BOOST_BINARY(00)); 34 | BOOST_TEST_EQ(p.bit(0), false); 35 | BOOST_TEST_EQ(p.bit(1), false); 36 | BOOST_TEST_EQ((*p).a, 2); 37 | BOOST_TEST_EQ(p->b, 3); 38 | 39 | p.bits(BOOST_BINARY(01)); 40 | BOOST_TEST_EQ(p.bits(), BOOST_BINARY(01)); 41 | BOOST_TEST_EQ(p->a, 2); 42 | BOOST_TEST_EQ(p->b, 3); 43 | p.bits(BOOST_BINARY(10)); 44 | BOOST_TEST_EQ(p.bits(), BOOST_BINARY(10)); 45 | BOOST_TEST_EQ(p->a, 2); 46 | BOOST_TEST_EQ(p->b, 3); 47 | 48 | p.bit(0, false); 49 | p.bit(1, true); 50 | BOOST_TEST_EQ(p.bit(0), false); 51 | BOOST_TEST_EQ(p.bit(1), true); 52 | BOOST_TEST_EQ(p->a, 2); 53 | BOOST_TEST_EQ(p->b, 3); 54 | p.bit(0, true); 55 | p.bit(1, false); 56 | BOOST_TEST_EQ(p.bit(0), true); 57 | BOOST_TEST_EQ(p.bit(1), false); 58 | BOOST_TEST_EQ(p->a, 2); 59 | BOOST_TEST_EQ(p->b, 3); 60 | 61 | p.reset(); 62 | BOOST_TEST(p == (tagged_ptr())); 63 | } 64 | BOOST_TEST_EQ(destructor_count_test_type, 1); 65 | 66 | destructor_count_test_type = 0; 67 | { // basic usage of dynamic-sized array version 68 | auto a = make_tagged(10, 2, 3); 69 | 70 | BOOST_TEST(!!a); 71 | BOOST_TEST_EQ(a.size(), 10); 72 | BOOST_TEST_EQ(a->a, 2); 73 | BOOST_TEST_EQ(a->b, 3); 74 | BOOST_TEST_EQ(a[0].a, 2); 75 | BOOST_TEST_EQ(a[0].b, 3); 76 | BOOST_TEST_EQ(a[9].a, 2); 77 | BOOST_TEST_EQ(a[9].b, 3); 78 | } 79 | BOOST_TEST_EQ(destructor_count_test_type, 10); 80 | 81 | destructor_count_test_type = 0; 82 | { // basic usage of fixed-sized array version 83 | auto a = make_tagged(2, 3); 84 | 85 | BOOST_TEST(!!a); 86 | BOOST_TEST_EQ(a.size(), 10); 87 | BOOST_TEST_EQ(a->a, 2); 88 | BOOST_TEST_EQ(a->b, 3); 89 | BOOST_TEST_EQ(a[0].a, 2); 90 | BOOST_TEST_EQ(a[0].b, 3); 91 | BOOST_TEST_EQ(a[9].a, 2); 92 | BOOST_TEST_EQ(a[9].b, 3); 93 | } 94 | BOOST_TEST_EQ(destructor_count_test_type, 10); 95 | 96 | destructor_count_test_type = 0; 97 | { // null vs default constructed 98 | auto p = tagged_ptr(); // null 99 | BOOST_TEST(p == (tagged_ptr())); 100 | p.bits(BOOST_BINARY(101)); 101 | BOOST_TEST(!p); // still nullptr... 102 | BOOST_TEST(p != (tagged_ptr())); // ... but not null 103 | 104 | auto q = make_tagged(); // default constructed 105 | BOOST_TEST(q != (tagged_ptr())); // not nullptr 106 | q.bits(BOOST_BINARY(101)); 107 | BOOST_TEST_EQ(p.bits(), q.bits()); 108 | } 109 | BOOST_TEST_EQ(destructor_count_test_type, 1); 110 | 111 | { // release 112 | destructor_count_test_type = 0; 113 | test_type *tp = nullptr; 114 | { 115 | auto p = make_tagged(2, 3); 116 | tp = p.release(); 117 | } 118 | BOOST_TEST_EQ(destructor_count_test_type, 0); 119 | delete tp; 120 | BOOST_TEST_EQ(destructor_count_test_type, 1); 121 | } 122 | 123 | destructor_count_test_type = 0; 124 | { // move ctor and assign 125 | auto p = make_tagged(2, 3); 126 | p.bit(0, false); 127 | p.bit(1, true); 128 | 129 | tagged_ptr q(std::move(p)); 130 | BOOST_TEST_EQ(q.bit(0), false); 131 | BOOST_TEST_EQ(q.bit(1), true); 132 | BOOST_TEST_EQ(q->a, 2); 133 | BOOST_TEST_EQ(q->b, 3); 134 | 135 | tagged_ptr r; 136 | r = std::move(q); 137 | BOOST_TEST_EQ(r.bit(0), false); 138 | BOOST_TEST_EQ(r.bit(1), true); 139 | BOOST_TEST_EQ(r->a, 2); 140 | BOOST_TEST_EQ(r->b, 3); 141 | } 142 | BOOST_TEST_EQ(destructor_count_test_type, 1); 143 | 144 | destructor_count_test_type = 0; 145 | { // swap 146 | auto p = make_tagged(2, 3); 147 | p.bits(BOOST_BINARY(101)); 148 | auto q = make_tagged(4, 5); 149 | q.bits(BOOST_BINARY(010)); 150 | 151 | std::swap(p, q); 152 | 153 | BOOST_TEST_EQ(p.bits(), BOOST_BINARY(010)); 154 | BOOST_TEST_EQ(p->a, 4); 155 | BOOST_TEST_EQ(p->b, 5); 156 | BOOST_TEST_EQ(q.bits(), BOOST_BINARY(101)); 157 | BOOST_TEST_EQ(q->a, 2); 158 | BOOST_TEST_EQ(q->b, 3); 159 | 160 | p.swap(q); 161 | 162 | BOOST_TEST_EQ(p.bits(), BOOST_BINARY(101)); 163 | BOOST_TEST_EQ(p->a, 2); 164 | BOOST_TEST_EQ(p->b, 3); 165 | BOOST_TEST_EQ(q.bits(), BOOST_BINARY(010)); 166 | BOOST_TEST_EQ(q->a, 4); 167 | BOOST_TEST_EQ(q->b, 5); 168 | } 169 | BOOST_TEST_EQ(destructor_count_test_type, 2); 170 | 171 | { // conversion derived <-> base 172 | static unsigned destructor_count_base = 0; 173 | struct base { 174 | int a = 1; 175 | virtual ~base() { ++destructor_count_base; } 176 | }; 177 | 178 | static unsigned destructor_count_derived = 0; 179 | struct derived : public base { 180 | char b = 2; 181 | virtual ~derived() { ++destructor_count_derived; } 182 | }; 183 | 184 | struct derived2 : public base {}; 185 | 186 | { 187 | auto d = make_tagged(); 188 | d.bits(BOOST_BINARY(101)); 189 | tagged_ptr b = std::move(d); 190 | BOOST_TEST_EQ(b.bits(), BOOST_BINARY(101)); 191 | BOOST_TEST_EQ(b->a, 1); 192 | auto dp = dynamic_cast(b.get()); 193 | BOOST_TEST(dp); 194 | BOOST_TEST_EQ(dp->a, 1); 195 | BOOST_TEST_EQ(dp->b, 2); 196 | auto d2p = dynamic_cast(b.get()); 197 | BOOST_TEST(!d2p); 198 | } 199 | BOOST_TEST_EQ(destructor_count_base, 1); 200 | BOOST_TEST_EQ(destructor_count_derived, 1); 201 | } 202 | return boost::report_errors(); 203 | } 204 | --------------------------------------------------------------------------------