├── .travis.yml ├── .editorconfig ├── .gitignore ├── README.md ├── tests ├── tests.cpp ├── container_matcher.h ├── array2d.cpp ├── fixed_map.cpp ├── fixed_string.cpp ├── ring_buffer.cpp ├── inlined_vector.cpp └── object_pool.cpp ├── .clang-format ├── LICENSE.md ├── Makefile └── include ├── array2d.h ├── fixed_map.h ├── fixed_string.h ├── ring_buffer.h ├── inlined_vector.h └── object_pool.h /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: gcc 3 | script: 4 | - make 5 | - make test 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*.{h,cpp}] 3 | indent_style = tab 4 | indent_size = 4 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local 2 | .vscode/* 3 | 4 | # Build 5 | bin/* 6 | obj/* 7 | build/msvc/utils/Debug*/* 8 | build/msvc/utils/Release/* 9 | build/msvc/utils/.vs/* 10 | 11 | # OS generated files 12 | .DS_Store 13 | .DS_Store? 14 | ._* 15 | .Spotlight-V100 16 | .Trashes 17 | ehthumbs.db 18 | Thumbs.db 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++11 Data Structures [![Build Status](https://travis-ci.org/eigenbom/utils.svg?branch=master)](https://travis-ci.org/eigenbom/utils) 2 | 3 | A collection of data structures for c++11. Each header is standalone and can be configured with `#define`. Tests are provided in `tests/`. 4 | 5 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 6 | -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | #ifdef DEBUG_MEMORY 2 | #if defined(_WIN32) 3 | #define _CRTDBG_MAP_ALLOC 4 | #include 5 | #include 6 | #else 7 | #error DEBUG_MEMORY is not implemented for this platform. 8 | #endif 9 | 10 | #endif 11 | 12 | #define CATCH_CONFIG_RUNNER 13 | #include "catch.hpp" 14 | 15 | int main(int argc, char* argv[]) { 16 | #ifdef DEBUG_MEMORY 17 | _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); 18 | #endif 19 | int result = Catch::Session().run(argc, argv); 20 | return result; 21 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | PointerAlignment: Left 3 | ColumnLimit: 100 4 | AllowShortIfStatementsOnASingleLine: false 5 | # FixNamespaceComments: true 6 | BraceWrapping: 7 | AfterClass: false 8 | AfterControlStatement: false 9 | AfterEnum: false 10 | AfterFunction: false 11 | AfterNamespace: false 12 | AfterObjCDeclaration: false 13 | AfterStruct: false 14 | AfterUnion: false 15 | BeforeCatch: true 16 | BeforeElse: true 17 | IndentBraces: false 18 | AccessModifierOffset: -4 19 | BreakBeforeBraces: Custom 20 | IndentCaseLabels: false 21 | IndentWidth: 4 22 | TabWidth: 4 23 | UseTab: ForContinuationAndIndentation 24 | SortIncludes: true 25 | SpaceAfterCStyleCast: true 26 | SpaceBeforeAssignmentOperators: true 27 | SpaceAfterTemplateKeyword: false 28 | SpacesInAngles: false 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Benjamin Porter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tests/container_matcher.h: -------------------------------------------------------------------------------- 1 | #ifndef INLINED_VECTOR_TESTS_CONTAINER_MATCHER_H 2 | #define INLINED_VECTOR_TESTS_CONTAINER_MATCHER_H 3 | 4 | #include "catch.hpp" 5 | 6 | // A generic container comparator 7 | // Why doesn't Catch2 have this already? 8 | template 9 | struct EqualsMatcher : Catch::MatcherBase { 10 | EqualsMatcher(Container2 const &comparator) : m_comparator( comparator ) {} 11 | 12 | bool match(Container1 const &v) const override { 13 | auto it = v.begin(); 14 | auto it2 = m_comparator.begin(); 15 | while (true){ 16 | if (it == v.end() && it2 == m_comparator.end()) return true; 17 | if (it == v.end() || it2 == m_comparator.end()) return false; 18 | if (*it != *it2) return false; 19 | it++, it2++; 20 | } 21 | return true; 22 | } 23 | std::string describe() const override { 24 | return "Equals: " + ::Catch::Detail::stringify( m_comparator ); 25 | } 26 | Container2 const& m_comparator; 27 | }; 28 | 29 | template 30 | EqualsMatcher Equals(C1 const&, C2 const& comparator ) { 31 | return EqualsMatcher( comparator ); 32 | } 33 | 34 | #endif -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | SOURCE_DIR = tests 3 | INCLUDE_DIR = include 4 | BIN_DIR_ROOT = bin 5 | OBJ_DIR_ROOT = obj 6 | EXAMPLES_DIR = examples 7 | CXXFLAGS = -fpermissive -std=c++11 -Wall 8 | 9 | DEBUG ?= 0 10 | ifeq ($(DEBUG), 1) 11 | CXXFLAGS += -g3 -DDEBUG 12 | CONFIG = debug 13 | else 14 | CXXFLAGS += -DNDEBUG 15 | CONFIG = release 16 | endif 17 | 18 | OBJ_DIR = $(OBJ_DIR_ROOT)/$(CONFIG) 19 | BIN_DIR = $(BIN_DIR_ROOT)/$(CONFIG) 20 | 21 | OBJS = tests.o \ 22 | array2d.o \ 23 | inlined_vector.o \ 24 | fixed_map.o \ 25 | fixed_string.o \ 26 | object_pool.o \ 27 | ring_buffer.o 28 | 29 | ALL_OBJS = $(addprefix $(OBJ_DIR)/, $(OBJS)) 30 | 31 | .PHONY: all clean test build-test run-test examples bin-dir 32 | all: build-test 33 | 34 | $(OBJ_DIR)/%.o: $(SOURCE_DIR)/%.cpp $(INCLUDE_DIR)/%.h 35 | @mkdir -p $(OBJ_DIR) 36 | $(CXX) -o $@ -c $< -I $(INCLUDE_DIR) $(CXXFLAGS) 37 | 38 | $(OBJ_DIR)/tests.o: $(SOURCE_DIR)/tests.cpp 39 | @mkdir -p $(OBJ_DIR) 40 | $(CXX) -o $@ -c $< -I $(INCLUDE_DIR) $(CXXFLAGS) 41 | 42 | clean: 43 | rm -rf $(OBJ_DIR_ROOT) 44 | rm -rf $(BIN_DIR_ROOT) 45 | 46 | test: $(BIN_DIR)/test run-test 47 | 48 | build-test: $(BIN_DIR)/test 49 | 50 | bin-dir: 51 | @mkdir -p $(BIN_DIR) 52 | 53 | $(BIN_DIR)/test: $(ALL_OBJS) Makefile bin-dir 54 | $(CXX) -o $@ $(ALL_OBJS) -I $(INCLUDE_DIR) $(CXXFLAGS) $(LDFLAGS) 55 | 56 | run-test: 57 | $(BIN_DIR)/test 58 | 59 | examples: $(BIN_DIR)/moving_average 60 | 61 | $(BIN_DIR)/moving_average: $(EXAMPLES_DIR)/moving_average/main.cpp Makefile bin-dir 62 | $(CXX) -o $@ $(EXAMPLES_DIR)/moving_average/main.cpp -I $(INCLUDE_DIR) $(CXXFLAGS) $(LDFLAGS) 63 | 64 | -------------------------------------------------------------------------------- /tests/array2d.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define BSP_ARRAY2D_ALLOCATION(array, bytes) {\ 5 | /* if (bytes > 1024 * 1024){ */ \ 6 | std::cout << "resizing array2d<" << typeid((array)(0,0)).name() << ">" \ 7 | << "(" << (array).width() << "x" << (array).height() << ") " \ 8 | << bytes << "B\n";} \ 9 | /* } */ 10 | 11 | #include "../include/array2d.h" 12 | 13 | #include "catch.hpp" 14 | #include "container_matcher.h" 15 | 16 | using bsp::array2d; 17 | using Catch::Equals; 18 | 19 | TEST_CASE("array2d basics", "[array2d]") { 20 | array2d arr { 2, 2, 42 }; 21 | CHECK(arr.width() == 2); 22 | CHECK(arr.height() == 2); 23 | CHECK(arr(0,0) == 42); 24 | CHECK(arr(1,0) == 42); 25 | CHECK(arr(0,1) == 42); 26 | CHECK(arr(1,1) == 42); 27 | CHECK(arr(-1,0) == 0); 28 | 29 | arr.resize(4, 2, 42); 30 | CHECK(arr.width() == 4); 31 | CHECK(arr.height() == 2); 32 | CHECK(arr(0,0) == 42); 33 | } 34 | 35 | TEST_CASE("array2d construction", "[array2d]") { 36 | SECTION("default construction"){ 37 | array2d arr; 38 | CHECK(arr.width() == 0); 39 | CHECK(arr.height() == 0); 40 | } 41 | 42 | SECTION("copy construction"){ 43 | array2d arr {8, 8, 0}; 44 | array2d arr2 {arr}; 45 | CHECK(arr2.width() == 8); 46 | CHECK(arr2.height() == 8); 47 | } 48 | 49 | SECTION("copy assignment"){ 50 | array2d arr {8, 8, 0}; 51 | array2d arr2; 52 | arr2 = arr; 53 | CHECK(arr2.width() == 8); 54 | CHECK(arr2.height() == 8); 55 | } 56 | 57 | SECTION("move construction"){ 58 | array2d arr {8, 8, 0}; 59 | array2d arr2 {std::move(arr)}; 60 | CHECK(arr2.width() == 8); 61 | CHECK(arr2.height() == 8); 62 | CHECK(arr.width() == 0); 63 | CHECK(arr.height() == 0); 64 | } 65 | 66 | SECTION("move assignment"){ 67 | array2d arr {8, 8, 0}; 68 | array2d arr2; 69 | arr2 = std::move(arr); 70 | CHECK(arr2.width() == 8); 71 | CHECK(arr2.height() == 8); 72 | CHECK(arr.width() == 0); 73 | CHECK(arr.height() == 0); 74 | } 75 | } 76 | 77 | 78 | TEST_CASE("array2d operator<<", "[array2d]") { 79 | array2d identity { 2, 2 }; 80 | identity(0, 0) = 1; 81 | identity(1, 1) = 1; 82 | std::cout << "Should print the identity array2d...\n"; 83 | std::cout << identity << "\n"; 84 | } -------------------------------------------------------------------------------- /tests/fixed_map.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // #define BSP_FIXED_MAP_THROWS 14 | #define BSP_FIXED_MAP_LOG_ERROR(message) std::cerr << message << "\n" 15 | #include "../include/fixed_map.h" 16 | 17 | #include "catch.hpp" 18 | #include "container_matcher.h" 19 | 20 | using bsp::fixed_map; 21 | using Catch::Equals; 22 | 23 | TEST_CASE("fixed_map basics", "[fixed_map]") { 24 | fixed_map map; 25 | CHECK(map.size() == 0); 26 | CHECK(map.empty()); 27 | 28 | map.insert(0, 0); 29 | CHECK(map.size() == 1); 30 | CHECK(map[0] == 0); 31 | 32 | map.insert(1, 42); 33 | CHECK(map.size() == 2); 34 | CHECK(map[1] == 42); 35 | 36 | map.clear(); 37 | CHECK(map.size() == 0); 38 | } 39 | 40 | TEST_CASE("fixed_map construction", "[fixed_map]") { 41 | SECTION("default construction"){ 42 | fixed_map map; 43 | } 44 | 45 | SECTION("initialiser list construction"){ 46 | fixed_map map { {0, 0}, {1, 42} }; 47 | CHECK(map[0] == 0); 48 | CHECK(map[1] == 42); 49 | } 50 | 51 | SECTION("container construction"){ 52 | std::vector> els { {0, 0}, {1, 42} }; 53 | fixed_map map { els }; 54 | CHECK(map[0] == 0); 55 | CHECK(map[1] == 42); 56 | } 57 | 58 | SECTION("initialiser list construction"){ 59 | fixed_map map { {"hp", 16}, {"mp", 7}, {"int", 3} }; 60 | CHECK(map["hp"] == 16); 61 | } 62 | 63 | SECTION("container construction"){ 64 | std::vector> els { {"hp", 16}, {"mp", 7}, {"int", 3} }; 65 | fixed_map map { els }; 66 | CHECK(map["hp"] == 16); 67 | } 68 | 69 | SECTION("copy construction"){ 70 | fixed_map map1 { {0, 0}, {1, 42} }; 71 | fixed_map map2 {map1}; 72 | CHECK(map2[0] == 0); 73 | CHECK(map2[1] == 42); 74 | } 75 | 76 | SECTION("move construction"){ 77 | fixed_map map1 { {0, 0}, {1, 42} }; 78 | fixed_map map2 {std::move(map1)}; 79 | CHECK(map2[0] == 0); 80 | CHECK(map2[1] == 42); 81 | } 82 | } 83 | 84 | TEST_CASE("fixed_map operator<<", "[fixed_map]") { 85 | std::cout << "testing operator<<...\n"; 86 | fixed_map m1 { {0, 0}, {1, 42}, {6, 70} }; 87 | std::cout << m1 << "\n"; 88 | 89 | fixed_map m2 { {"hp", 16}, {"mp", 7}, {"int", 3} }; 90 | std::cout << m2 << "\n"; 91 | } 92 | 93 | TEST_CASE("fixed_map exceptions", "[fixed_map]") { 94 | CHECK_THROWS(fixed_map({ {0, 0}, {1, 42}, {6, 70} })); 95 | 96 | std::vector> els { {0, 0}, {1, 42}, {6, 70} }; 97 | CHECK_THROWS(fixed_map(els)); 98 | } 99 | 100 | TEST_CASE("fixed_map exception in insertion", "[fixed_map]") { 101 | fixed_map map; 102 | map.insert(0, 0); 103 | map.insert(1, 1); 104 | CHECK_THROWS_AS(map.insert(2, 1), std::length_error); 105 | } 106 | -------------------------------------------------------------------------------- /include/array2d.h: -------------------------------------------------------------------------------- 1 | // Customise the behaviour of array2d by defining these before including it: 2 | // #define BSP_ARRAY2D_ALLOCATION(array, bytes) to receive allocations 3 | 4 | #ifndef BSP_ARRAY2D_H 5 | #define BSP_ARRAY2D_H 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace bsp { 13 | 14 | template class array2d { 15 | public: 16 | using value_type = T; 17 | using reference = T&; 18 | using const_reference = const T&; 19 | using size_type = int; 20 | 21 | public: 22 | array2d() = default; 23 | 24 | array2d(size_type width, size_type height, const T& value = T()) : width_(width), height_(height), data_(width_ * height_, value), null_value_() { 25 | assert(width >= 0 && height >= 0); 26 | } 27 | 28 | array2d(const array2d& other):width_(other.width_), height_(other.height_), data_(other.data_), null_value_(other.null_value_){} 29 | 30 | array2d& operator=(const array2d& other){ 31 | data_ = other.data_; 32 | width_ = other.width_; 33 | height_ = other.height_; 34 | null_value_ = other.null_value_; 35 | return *this; 36 | } 37 | 38 | array2d(array2d&& other):width_(other.width_), height_(other.height_), data_(std::move(other.data_)), null_value_(other.null_value_){ 39 | other.width_ = 0; 40 | other.height_ = 0; 41 | } 42 | 43 | array2d& operator=(array2d&& other){ 44 | data_ = std::move(other.data_); 45 | width_ = other.width_; 46 | height_ = other.height_; 47 | null_value_ = other.null_value_; 48 | other.width_ = 0; 49 | other.height_ = 0; 50 | return *this; 51 | } 52 | 53 | inline size_type width() const { return width_; } 54 | 55 | inline size_type height() const { return height_; } 56 | 57 | void resize(size_type width, size_type height, const T& value = T()){ 58 | assert(width >= 0 && height >= 0); 59 | 60 | #ifdef BSP_ARRAY2D_ALLOCATION 61 | int bytes = (sizeof(T) * width * height); 62 | BSP_ARRAY2D_ALLOCATION(*this, bytes); 63 | #endif 64 | 65 | width_ = width; 66 | height_ = height; 67 | data_.resize(width_ * height_); 68 | fill(value); 69 | null_value_ = T(); 70 | } 71 | 72 | inline void fill(const T& t) { 73 | std::fill(data_.begin(), data_.end(), t); 74 | } 75 | 76 | inline reference operator()(size_type i, size_type j) { 77 | return valid_index(i, j) ? data_[index(i,j)] : null_value_; 78 | } 79 | 80 | inline const_reference operator()(size_type i, size_type j) const { 81 | return valid_index(i, j) ? data_[index(i,j)] : null_value_; 82 | } 83 | 84 | inline bool valid_index(size_type i, size_type j) const { 85 | return i >= 0 && i < width_ && j >= 0 && j < height_; 86 | } 87 | 88 | protected: 89 | size_type width_ = 0; 90 | size_type height_ = 0; 91 | std::vector data_; 92 | T null_value_; 93 | 94 | protected: 95 | inline size_type index(size_type i, size_type j) const { return i + j*width_; } 96 | 97 | template 98 | friend std::ostream& operator<<(std::ostream&, const array2d&); 99 | }; 100 | 101 | template 102 | inline std::ostream& operator<<(std::ostream& out, const array2d& array) { 103 | using size_type = typename array2d::size_type; 104 | 105 | out << "array2d {"; 106 | for (size_type row = 0; row < array.height_; row++){ 107 | out << "{"; 108 | for (size_type col = 0; col < array.width_; col++){ 109 | out << array(col, row); 110 | if (col < array.width_ - 1) out << ", "; 111 | } 112 | out << "}"; 113 | if (row < array.height_ - 1) out << ", "; 114 | } 115 | out << "}"; 116 | return out; 117 | } 118 | 119 | } 120 | 121 | #endif -------------------------------------------------------------------------------- /include/fixed_map.h: -------------------------------------------------------------------------------- 1 | #ifndef BSP_FIXED_MAP_H 2 | #define BSP_FIXED_MAP_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace bsp { 13 | 14 | namespace detail { 15 | template struct is_iterator : std::false_type {}; 16 | template 17 | struct is_iterator< 18 | T_, typename std::enable_if< 19 | std::is_base_of::iterator_category>::value || 21 | std::is_same::iterator_category>::value>::type> 23 | : std::true_type {}; 24 | } // namespace detail 25 | 26 | // A simple map of elements stored in a fixed-size array. 27 | // Is essentially a hashmap with open addressing and linear probing. 28 | template> 29 | class fixed_map { 30 | static_assert(Capacity > 0, "Capacity <= 0!"); 31 | 32 | public: 33 | struct slot { 34 | Key key; 35 | T value; 36 | bool valid = false; 37 | }; 38 | 39 | using array_type = std::array; 40 | 41 | using key_type = Key; 42 | using mapped_type = T; 43 | using value_type = slot; 44 | using reference = T&; 45 | using const_reference = const T&; 46 | using iterator = typename array_type::iterator; 47 | using const_iterator = typename array_type::const_iterator; 48 | using size_type = int; 49 | 50 | public: 51 | fixed_map(const T& invalid_value = T()) : size_(0), invalid_value_(invalid_value) { clear(); } 52 | 53 | template fixed_map(const Container& els) : fixed_map(els.begin(), els.end()) {} 54 | 55 | fixed_map(std::initializer_list> list) 56 | : fixed_map(list.begin(), list.end()) {} 57 | 58 | inline void clear() { 59 | size_ = 0; 60 | std::fill(data_.begin(), data_.end(), value_type()); 61 | } 62 | 63 | inline bool empty() const { return size_ == 0; } 64 | 65 | inline size_type size() const { return size_; } 66 | 67 | static constexpr inline size_type max_size() { return Capacity; } 68 | 69 | bool has(const key_type& key) const { 70 | return find_index(key) != -1; 71 | } 72 | 73 | inline const_reference find(const key_type& key) const { 74 | auto index = find_index(key); 75 | if (index != -1) return data_[index].value; 76 | else return invalid_value_; 77 | } 78 | 79 | inline reference find(const key_type& key) { 80 | auto index = find_index(key); 81 | if (index != -1) return data_[index].value; 82 | else return invalid_value_; 83 | } 84 | 85 | reference operator[](const key_type& key) { return find(key); } 86 | 87 | const_reference operator[](const key_type& key) const { return find(key); } 88 | 89 | template iterator insert(const Key_& key, const T& value) { 90 | if (size_ >= max_size()) { 91 | throw std::length_error("fixed_map: trying to insert too many elements"); 92 | } 93 | size_type index = hash_to_index(key); 94 | size_type oindex = index; 95 | while (data_[index].valid) { 96 | index = (index + 1) % max_size(); 97 | if (index == oindex) { 98 | // TODO: This should be unreachable? 99 | assert(false); 100 | return begin(); 101 | } 102 | } 103 | data_[index].key = key; 104 | data_[index].value = value; 105 | data_[index].valid = true; 106 | size_++; 107 | return std::next(data_.begin(), index); 108 | } 109 | 110 | iterator begin() { return data_.begin(); } 111 | iterator end() { return begin() + max_size(); } 112 | 113 | const_iterator begin() const { return data_.begin(); } 114 | const_iterator end() const { return begin() + max_size(); } 115 | 116 | protected: 117 | size_type size_ = 0; 118 | array_type data_; 119 | T invalid_value_; 120 | 121 | protected: 122 | template::value>::type> 124 | fixed_map(Iter begin_, Iter end_) { 125 | auto size = static_cast(std::distance(begin_, end_)); 126 | if (size > max_size()) throw std::length_error("fixed_map: too many elements"); 127 | for (auto it = begin_; it != end_; ++it) { 128 | insert(it->first, it->second); 129 | } 130 | } 131 | 132 | static inline std::size_t hash(const key_type& key) { return Hash{}(key); } 133 | 134 | static inline size_type hash_to_index(const key_type& key) { return static_cast(hash(key) % max_size()); } 135 | 136 | inline size_type find_index(const key_type& key) const { 137 | auto start_index = hash_to_index(key); 138 | auto index = start_index; 139 | do { 140 | const auto& slot = data_[index]; 141 | if (slot.valid && slot.key == key) { 142 | return index; 143 | } 144 | index = (index + 1) % max_size(); 145 | } 146 | while (index != start_index); 147 | return -1; 148 | } 149 | 150 | template 151 | friend std::ostream& operator<<(std::ostream&, const fixed_map&); 152 | }; 153 | 154 | template 155 | inline std::ostream& operator<<(std::ostream& out, 156 | const fixed_map& map) { 157 | out << "fixed_map<" << Capacity_ << "> {"; 158 | if (map.empty()) 159 | out << "}"; 160 | else { 161 | for (auto it = map.data_.begin(); it != map.data_.end(); ++it) { 162 | const auto& el = *it; 163 | if (el.valid) 164 | out << el.key << ": " << el.value; 165 | else 166 | out << "_"; 167 | if (std::next(it) != map.data_.end()) 168 | out << ", "; 169 | } 170 | out << "}"; 171 | } 172 | return out; 173 | } 174 | 175 | } // namespace bsp 176 | 177 | #endif -------------------------------------------------------------------------------- /include/fixed_string.h: -------------------------------------------------------------------------------- 1 | // Customise the behaviour of fixed_string by defining these before including it: 2 | // - #define BSP_FIXED_STRING_ZERO_CONTENTS 3 | // - #define BSP_FIXED_STRING_THROWS 4 | // - #define BSP_FIXED_STRING_LOG_ERROR(message) to log errors 5 | 6 | #ifndef BSP_FIXED_STRING_H 7 | #define BSP_FIXED_STRING_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace bsp { 17 | 18 | namespace detail { 19 | template< class InputIt1, class InputIt2 > 20 | bool equal( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2){ 21 | return std::equal(first1, last1, first2); 22 | } 23 | } 24 | 25 | // A fixed-sized string that can store N characters (excluding the null-terminator) 26 | // E.g., fixed_string<4> has a buffer of 5 characters 27 | // Main use is to store small constant strings. 28 | template class fixed_string { 29 | static_assert(Capacity > 0, "Capacity <= 0!"); 30 | 31 | public: 32 | using array_type = std::array; 33 | using iterator = typename array_type::iterator; 34 | using const_iterator = typename array_type::const_iterator; 35 | using size_type = int; 36 | 37 | public: 38 | fixed_string():size_(0){ zero_contents(); data_[0] = '\0'; } 39 | explicit fixed_string(bool truncates):size_(0), truncates_(truncates) { zero_contents(); data_[0] = '\0'; } 40 | 41 | fixed_string(const fixed_string&) = default; 42 | fixed_string(fixed_string&&) = default; 43 | fixed_string& operator=(const fixed_string& str) = default; 44 | fixed_string& operator=(fixed_string&& str) = default; 45 | 46 | fixed_string(const char* str, bool truncates = false):fixed_string(str, str+strlen(str), truncates) {} 47 | template fixed_string(const String& str, bool truncates = false):fixed_string(std::begin(str), std::end(str), truncates) {} 48 | 49 | fixed_string& operator=(const char* str) { return assign(str, str + strlen(str)); } 50 | template fixed_string& operator=(const String& str) { 51 | using std::begin; 52 | using std::end; 53 | return assign(begin(str), end(str)); 54 | } 55 | 56 | std::string str() const { return std::string(begin(), end()); } 57 | const char* c_str() const { return data_.data(); } 58 | 59 | inline bool truncates() const { return truncates_; } 60 | 61 | void clear() { size_ = 0; data_[0] = '\0'; } 62 | bool empty() const { return size_ == 0; } 63 | size_type size() const { return size_; } 64 | static constexpr inline size_type max_size() { return Capacity; } 65 | 66 | void set_size(size_type size) { assert(size <= max_size()); size_ = size; } 67 | 68 | char& operator[](size_type index) { return data_[index]; } 69 | const char& operator[](size_type index) const { return data_[index]; } 70 | 71 | iterator begin() { return data_.begin(); } 72 | iterator end() { return std::next(begin(), size_); } 73 | 74 | const_iterator begin() const { return data_.begin(); } 75 | const_iterator end() const { return std::next(begin(), size_); } 76 | 77 | void assign_to(std::string& str) const { str.assign(begin(), end()); } 78 | 79 | template 80 | fixed_string& assign(Iter begin_, Iter end_){ 81 | auto size = static_cast(std::distance(begin_, end_)); 82 | if (size > max_size() && !truncates_) error("fixed_string is too long!"); 83 | zero_contents(); 84 | size_ = std::min(size, max_size()); 85 | std::copy(begin_, std::next(begin_, size_), data_.begin()); 86 | data_[size_] = '\0'; 87 | size_ = static_cast(std::distance(begin(), std::find(begin(), end(), '\0'))); 88 | return *this; 89 | } 90 | 91 | protected: 92 | std::array data_; 93 | size_type size_ = 0; 94 | bool truncates_ = false; 95 | 96 | protected: 97 | // Helper constructor 98 | template 99 | fixed_string(Iter begin_, Iter end_, bool truncates = false):truncates_(truncates) { 100 | auto size = static_cast(std::distance(begin_, end_)); 101 | if (size > max_size() && !truncates_) error("fixed_string is too long!"); 102 | zero_contents(); 103 | size_ = std::min(size, max_size()); 104 | std::copy(begin_, std::next(begin_, size_), data_.begin()); 105 | data_[size_] = '\0'; 106 | size_ = static_cast(std::distance(begin(), std::find(begin(), end(), '\0'))); 107 | } 108 | 109 | inline void zero_contents(){ 110 | #ifdef BSP_FIXED_STRING_ZERO_CONTENTS 111 | data_.fill('\0'); 112 | #endif 113 | } 114 | 115 | void error(const char* message) const { 116 | #ifdef BSP_FIXED_STRING_LOG_ERROR 117 | BSP_FIXED_STRING_LOG_ERROR(message); 118 | #endif 119 | #ifdef BSP_FIXED_STRING_THROWS 120 | throw std::runtime_error(message); 121 | #endif 122 | } 123 | 124 | template friend std::ostream& operator<<(std::ostream& out, const fixed_string& str); 125 | }; 126 | 127 | template 128 | bool operator==(const fixed_string& lhs, const fixed_string& rhs) { 129 | return lhs.size() == rhs.size() && detail::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 130 | } 131 | 132 | template 133 | bool operator==(const fixed_string& lhs, const std::string& rhs) { 134 | return lhs.size() == rhs.size() && detail::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 135 | } 136 | 137 | template 138 | bool operator==(const fixed_string& lhs, const char* rhs) { 139 | return lhs.size() == strlen(rhs) && detail::equal(lhs.begin(), lhs.end(), rhs, rhs + lhs.size()); 140 | } 141 | 142 | template 143 | bool operator<(const fixed_string& lhs, const fixed_string& rhs) { 144 | return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 145 | } 146 | 147 | template std::ostream& operator<<(std::ostream& out, const fixed_string& str) { 148 | return out << "fixed_string<" << M << ">\"" << str.c_str() << "\""; 149 | } 150 | 151 | } // namespace bsp 152 | 153 | #endif 154 | -------------------------------------------------------------------------------- /tests/fixed_string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // #define BSP_FIXED_STRING_ZERO_CONTENTS 14 | // #define BSP_FIXED_STRING_THROWS 15 | #define BSP_FIXED_STRING_LOG_ERROR(message) std::cerr << message << "\n" 16 | #include "../include/fixed_string.h" 17 | 18 | #include "catch.hpp" 19 | #include "container_matcher.h" 20 | 21 | using bsp::fixed_string; 22 | using Catch::Equals; 23 | 24 | TEST_CASE("fixed_string basic conversion needed for tests", "[fixed_string]") { 25 | fixed_string<8> str { "Hello" }; 26 | REQUIRE_THAT(str.str(), Equals("Hello")); 27 | REQUIRE_THAT(str.c_str(), Equals("Hello")); 28 | } 29 | 30 | TEST_CASE("fixed_string basics", "[fixed_string]") { 31 | 32 | SECTION("default constructed"){ 33 | fixed_string<8> str; 34 | CHECK(str.max_size() == 8); 35 | CHECK(str.size() == 0); 36 | CHECK(str.empty()); 37 | } 38 | 39 | SECTION("constructed from const char*"){ 40 | fixed_string<8> str { "Hello" }; 41 | CHECK(str.max_size() == 8); 42 | CHECK(str.size() == 5); 43 | CHECK(!str.empty()); 44 | CHECK(str[0] == 'H'); 45 | CHECK(str[1] == 'e'); 46 | CHECK(str[4] == 'o'); 47 | CHECK(str[5] == '\0'); 48 | CHECK_THAT(str.str(), Equals("Hello")); 49 | } 50 | 51 | 52 | SECTION("copied"){ 53 | fixed_string<8> str { "Hello" }; 54 | str = "Goodbye"; 55 | CHECK(str.size() == 7); 56 | CHECK_THAT(str.str(), Equals("Goodbye")); 57 | } 58 | 59 | SECTION("iterators"){ 60 | fixed_string<8> str { "Hello" }; 61 | CHECK(&*str.begin() == &str[0]); 62 | CHECK(std::distance(str.begin(), str.end()) == 5); 63 | } 64 | } 65 | 66 | TEST_CASE("fixed_string basic operations", "[fixed_string]") { 67 | SECTION ("construction"){ 68 | fixed_string<8> str { "Hello" }; 69 | CHECK_THAT(str.str(), Equals("Hello")); 70 | } 71 | 72 | SECTION ("construction"){ 73 | std::string hello = "Hello"; 74 | fixed_string<8> str { hello }; 75 | CHECK_THAT(str.str(), Equals("Hello")); 76 | } 77 | 78 | SECTION ("construction"){ 79 | fixed_string<8> hello = "Hello"; 80 | fixed_string<8> str { hello }; 81 | CHECK_THAT(str.str(), Equals("Hello")); 82 | } 83 | 84 | SECTION ("construction"){ 85 | fixed_string<8> hello = "Hello"; 86 | fixed_string<16> str { hello }; 87 | CHECK_THAT(str.str(), Equals("Hello")); 88 | } 89 | 90 | SECTION("constructed from char array"){ 91 | const char array[16] = {'\0'}; 92 | fixed_string<8> str {array}; 93 | CHECK(str.max_size() == 8); 94 | CHECK(str.size() == 0); 95 | CHECK(str.empty()); 96 | } 97 | 98 | SECTION ("copy construction"){ 99 | fixed_string<8> hello = "Hello"; 100 | fixed_string<8> str = hello; 101 | CHECK_THAT(str.str(), Equals("Hello")); 102 | } 103 | 104 | SECTION("copy construction"){ 105 | const char array[16] = {'\0'}; 106 | fixed_string<8> str = array; 107 | CHECK(str.empty()); 108 | } 109 | 110 | SECTION("copy construction"){ 111 | char array[16] = {'\0'}; 112 | fixed_string<8> str = array; 113 | CHECK(str.empty()); 114 | } 115 | 116 | SECTION ("copy assignment"){ 117 | fixed_string<8> hello = "Hello"; 118 | fixed_string<8> str; 119 | str = hello; 120 | CHECK(str.size() == 5); 121 | CHECK_THAT(str.str(), Equals("Hello")); 122 | } 123 | 124 | SECTION("copy assignment"){ 125 | const char array[16] = {'\0'}; 126 | fixed_string<8> str {true}; 127 | str = array; 128 | CHECK(str.empty()); 129 | } 130 | 131 | SECTION("copy assignment"){ 132 | char array[16] = {'\0'}; 133 | fixed_string<8> str {true}; 134 | str = array; 135 | CHECK(str.empty()); 136 | } 137 | 138 | SECTION ("move construction"){ 139 | fixed_string<8> hello = "Hello"; 140 | fixed_string<8> str { std::move(hello) }; 141 | CHECK_THAT(str.str(), Equals("Hello")); 142 | } 143 | 144 | SECTION ("move assignment"){ 145 | fixed_string<16> hello = "Hello"; 146 | fixed_string<8> str {false}; 147 | str = std::move(hello); 148 | CHECK_THAT(str.str(), Equals("Hello")); 149 | } 150 | 151 | SECTION ("move assignment"){ 152 | fixed_string<16> hello = "Hello"; 153 | fixed_string<4> str {true}; 154 | str = std::move(hello); 155 | CHECK_THAT(str.str(), Equals("Hell")); 156 | } 157 | 158 | SECTION("assign_to"){ 159 | fixed_string<8> str { "Hello" }; 160 | std::string output; 161 | str.assign_to(output); 162 | CHECK_THAT(output, Equals("Hello")); 163 | } 164 | } 165 | 166 | TEST_CASE("fixed_string truncates", "[fixed_string]") { 167 | SECTION ("construction"){ 168 | fixed_string<8> str { "Hello World", true }; 169 | CHECK(str.size() == 8); 170 | CHECK_THAT(str.str(), Equals("Hello Wo")); 171 | } 172 | 173 | SECTION ("copy assignment"){ 174 | fixed_string<8> str { true }; 175 | str = "Hello World"; 176 | CHECK(str.size() == 8); 177 | CHECK_THAT(str.str(), Equals("Hello Wo")); 178 | } 179 | } 180 | 181 | #ifdef BSP_FIXED_STRING_THROWS 182 | 183 | TEST_CASE("fixed_string too many characters", "[fixed_string]") { 184 | SECTION ("construction"){ 185 | std::cout << "Should print an error...\n"; 186 | CHECK_THROWS(fixed_string<8> { "Hello World" }); 187 | } 188 | 189 | SECTION ("construction"){ 190 | fixed_string<16> str1 { "Hello World" }; 191 | std::cout << "Should print an error...\n"; 192 | CHECK_THROWS(fixed_string<8> {str1}); 193 | } 194 | 195 | SECTION ("copy assignment"){ 196 | fixed_string<8> str; 197 | std::cout << "Should print an error...\n"; 198 | CHECK_THROWS(str = "Hello World"); 199 | } 200 | } 201 | 202 | #else 203 | 204 | TEST_CASE("fixed_string too many characters", "[fixed_string]") { 205 | SECTION ("construction"){ 206 | std::cout << "Should print an error...\n"; 207 | fixed_string<8> str { "Hello World" }; 208 | CHECK(str.size() == 8); 209 | CHECK_THAT(str.str(), Equals("Hello Wo")); 210 | } 211 | 212 | SECTION ("construction"){ 213 | fixed_string<16> str1 { "Hello World" }; 214 | std::cout << "Should print an error...\n"; 215 | fixed_string<8> str2 {str1}; 216 | CHECK(str2.size() == 8); 217 | CHECK_THAT(str2.str(), Equals("Hello Wo")); 218 | } 219 | 220 | SECTION ("copy assignment"){ 221 | fixed_string<8> str; 222 | std::cout << "Should print an error...\n"; 223 | str = "Hello World"; 224 | CHECK(str.size() == 8); 225 | CHECK_THAT(str.str(), Equals("Hello Wo")); 226 | } 227 | } 228 | 229 | #endif -------------------------------------------------------------------------------- /include/ring_buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BSP_RING_BUFFER_H 2 | #define BSP_RING_BUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace bsp { 14 | 15 | namespace detail_ring_buffer { 16 | template struct is_iterator : std::false_type {}; 17 | template 18 | struct is_iterator< 19 | T_, typename std::enable_if< 20 | std::is_base_of::iterator_category>::value || 22 | std::is_same::iterator_category>::value>::type> 24 | : std::true_type {}; 25 | 26 | template 27 | class ring_buffer_iterator : public std::iterator { 28 | public: 29 | using reference = typename ring_buffer::reference; 30 | using const_reference = typename ring_buffer::const_reference; 31 | using size_type = typename ring_buffer::size_type; 32 | 33 | public: 34 | ring_buffer_iterator(ring_buffer& ring):ring_(ring), offset_(ring.start()),i_(ring.count()) {} 35 | ring_buffer_iterator(ring_buffer& ring, size_type offset):ring_(ring),offset_(offset){} 36 | ring_buffer_iterator& operator++(){ ++i_; return *this; } 37 | ring_buffer_iterator operator++(int){ ring_buffer_iterator tmp(*this); ++(*this); return tmp; } 38 | ring_buffer_iterator& operator--(){ i_ = (i_ + ring_.max_size() - 1) % ring_.max_size(); return *this; } 39 | ring_buffer_iterator operator--(int){ ring_buffer_iterator tmp(*this); --(*this); return tmp; } 40 | bool operator==(const ring_buffer_iterator& rhs) const { return &ring_ == &rhs.ring_ && i_ == rhs.i_; } 41 | bool operator!=(const ring_buffer_iterator& rhs) const { return !(*this == rhs); } 42 | reference operator*() { return ring_[(i_ + offset_) % ring_.max_size()]; } 43 | const_reference operator*() const { return ring_[(i_ + offset_) % ring_.max_size()]; } 44 | 45 | protected: 46 | ring_buffer& ring_; 47 | size_type offset_ = 0; 48 | size_type i_ = 0; 49 | 50 | friend class const_iterator; 51 | }; 52 | 53 | template 54 | class ring_buffer_const_iterator : public std::iterator { 55 | public: 56 | using reference = typename ring_buffer::const_reference; 57 | using size_type = typename ring_buffer::size_type; 58 | 59 | public: 60 | ring_buffer_const_iterator(const ring_buffer& ring):ring_(ring), i_(ring.count()), offset_(ring.start()) {} 61 | ring_buffer_const_iterator(const ring_buffer& ring, size_type offset):ring_(ring),offset_(offset){} 62 | ring_buffer_const_iterator(const ring_buffer_iterator& it):ring_(it.ring_), offset_(it.offset_), i_(it.i_){} 63 | ring_buffer_const_iterator& operator++(){ i_++; return *this; } 64 | ring_buffer_const_iterator operator++(int){ ring_buffer_const_iterator tmp(*this); ++(*this); return tmp; } 65 | ring_buffer_const_iterator& operator--(){ i_ = (i_ + ring_.max_size() - 1) % ring_.max_size(); return *this; } 66 | ring_buffer_const_iterator operator--(int){ ring_buffer_const_iterator tmp(*this); --(*this); return tmp; } 67 | bool operator==(const ring_buffer_const_iterator& rhs) const { return &ring_ == &rhs.ring_ && i_ == rhs.i_; } 68 | bool operator!=(const ring_buffer_const_iterator& rhs) const { return !(*this == rhs); } 69 | reference operator*() const { return ring_[(i_ + offset_) % ring_.max_size()]; } 70 | 71 | protected: 72 | const ring_buffer& ring_; 73 | size_type offset_ = 0; 74 | size_type i_ = 0; 75 | }; 76 | } 77 | 78 | template class ring_buffer { 79 | static_assert(Capacity > 0, "Capacity <= 0!"); 80 | 81 | public: 82 | using value_type = T; 83 | using reference = T&; 84 | using const_reference = const T&; 85 | using size_type = int; 86 | 87 | using iterator = detail_ring_buffer::ring_buffer_iterator; 88 | using const_iterator = detail_ring_buffer::ring_buffer_const_iterator; 89 | using reverse_iterator = std::reverse_iterator; 90 | using const_reverse_iterator = std::reverse_iterator; 91 | 92 | public: 93 | 94 | ring_buffer() = default; 95 | 96 | template 97 | ring_buffer(const Container& els):ring_buffer(els.begin(), els.end()){} 98 | ring_buffer(std::initializer_list list):ring_buffer(list.begin(), list.end()){} 99 | 100 | size_type start() const { return start_; } 101 | 102 | size_type count() const { return count_; } 103 | 104 | static constexpr inline size_type max_size() { return Capacity; } 105 | 106 | void clear() { 107 | count_ = 0; 108 | start_ = 0; 109 | } 110 | 111 | bool empty() const { return count_ == 0; } 112 | 113 | bool valid_index(size_type index) const { 114 | if (index >= start_) return (index - start_) < count_; 115 | else return (max_size() - (start_ - index)) < count_; 116 | } 117 | 118 | // Add an element to the end of the ring buffer 119 | template 120 | void push_back(U&& value){ 121 | size_type next_index = (start_ + count_) % max_size(); 122 | data_[next_index] = std::forward(value); 123 | if (count_ >= max_size()) 124 | start_ = (start_ + 1) % max_size(); 125 | else 126 | count_++; 127 | } 128 | 129 | template 130 | void emplace_back(Args&&... args) { 131 | size_type next_index = (start_ + count_) % max_size(); 132 | data_[next_index] = T(std::forward(args)...); 133 | if (count_ >= max_size()) 134 | start_ = (start_ + 1) % max_size(); 135 | else 136 | count_++; 137 | } 138 | 139 | void pop_front(){ 140 | assert(count_ > 0); 141 | start_ = (start_ + 1) % max_size(); 142 | count_--; 143 | } 144 | 145 | reference front(){ return data_[start_]; } 146 | const_reference front() const { return data_[start_]; } 147 | 148 | reference back(){ return count_ == 0 ? front() : data_[(start_ + count_ - 1) % max_size()]; } 149 | const_reference back() const { return count_ == 0 ? front() : data_[(start_ + count_ - 1) % max_size()]; } 150 | 151 | // Directly indexes into underlying array 152 | reference operator[](size_type index) { return data_[index]; } 153 | 154 | const_reference operator[](size_type index) const { return data_[index]; } 155 | 156 | reference at(size_type index) { return const_cast(static_cast(this)->at(index)); } 157 | const_reference at(size_type index) const { 158 | if (index >= 0 && index < count_) { 159 | return data_[index]; 160 | } 161 | else { 162 | throw std::out_of_range("ring_buffer: accessing invalid element"); 163 | } 164 | } 165 | 166 | iterator begin() { return iterator(*this, start()); } 167 | 168 | iterator end() { return iterator(*this); } 169 | 170 | const_iterator begin() const { return const_iterator(*this, start()); } 171 | 172 | const_iterator end() const { return const_iterator(*this); } 173 | 174 | const_iterator cbegin() const { return const_iterator(*this, start()); } 175 | 176 | const_iterator cend() const { return const_iterator(*this); } 177 | 178 | reverse_iterator rbegin() { return std::reverse_iterator(end()); } 179 | 180 | reverse_iterator rend() { return std::reverse_iterator(begin()); } 181 | 182 | const_reverse_iterator rbegin() const { return std::reverse_iterator(end()); } 183 | 184 | const_reverse_iterator rend() const { return std::reverse_iterator(begin()); } 185 | 186 | protected: 187 | std::array data_; 188 | size_type count_ = 0; 189 | size_type start_ = 0; 190 | 191 | protected: 192 | template ::value>::type> 193 | ring_buffer(Iter begin, Iter end){ 194 | for (auto it = begin; it != end; ++it) push_back(*it); 195 | } 196 | 197 | template 198 | friend std::ostream& operator<<(std::ostream&, const ring_buffer&); 199 | }; 200 | 201 | template std::ostream& operator<<(std::ostream& out, 202 | const ring_buffer& ring) { 203 | out << "ring_buffer<" << Capacity_ << "> {"; 204 | if (ring.empty()) 205 | out << "}"; 206 | else { 207 | using size_type = typename ring_buffer::size_type; 208 | for (size_type i = 0; i < ring.max_size(); ++i){ 209 | if (ring.valid_index(i)) out << ring[i]; 210 | else out << "_"; 211 | if (i != ring.max_size() - 1) out << ", "; 212 | } 213 | out << "}"; 214 | } 215 | return out; 216 | } 217 | 218 | } // namespace 219 | 220 | #endif 221 | -------------------------------------------------------------------------------- /tests/ring_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../include/ring_buffer.h" 14 | #include "catch.hpp" 15 | #include "container_matcher.h" 16 | 17 | using bsp::ring_buffer; 18 | using Catch::Equals; 19 | 20 | TEST_CASE("ring_buffer basics", "[ring_buffer]") { 21 | ring_buffer ring; 22 | CHECK(ring.count() == 0); 23 | CHECK(ring.start() == 0); 24 | CHECK(ring.empty()); 25 | CHECK(ring.max_size() == 8); 26 | } 27 | 28 | TEST_CASE("ring_buffer basic operations", "[ring_buffer]"){ 29 | SECTION("clear"){ 30 | ring_buffer ring; 31 | ring.push_back(42); 32 | ring.clear(); 33 | CHECK(ring.count() == 0); 34 | CHECK(ring.empty()); 35 | } 36 | 37 | SECTION("can push_back"){ 38 | ring_buffer ring; 39 | ring.push_back(42); 40 | CHECK(ring.count() == 1); 41 | CHECK(ring.front() == 42); 42 | CHECK(ring.back() == 42); 43 | 44 | ring.push_back(-1); 45 | CHECK(ring.front() == 42); 46 | CHECK(ring.back() == -1); 47 | CHECK(ring.count() == 2); 48 | } 49 | 50 | SECTION("can emplace_back"){ 51 | ring_buffer, 8> ring; 52 | ring.emplace_back(new int(42)); 53 | CHECK(*ring.front() == 42); 54 | } 55 | 56 | SECTION("add up to max_size()"){ 57 | ring_buffer ring; 58 | for (int i=0; i<8; i++) ring.push_back(i); 59 | CHECK(ring.count() == 8); 60 | CHECK(ring.start() == 0); 61 | } 62 | 63 | SECTION("add beyond max_size()"){ 64 | ring_buffer ring; 65 | for (int i=0; i<8; i++) ring.push_back(i); 66 | ring.push_back(42); 67 | CHECK(ring.start() == 1); 68 | CHECK(ring.count() == 8); 69 | } 70 | 71 | SECTION("pushing and popping"){ 72 | ring_buffer ring; 73 | int size = 0; 74 | for (int i=0; i<16; i++){ 75 | ring.push_back(i); 76 | ring.push_back(i); 77 | CHECK(ring.back() == i); 78 | ring.pop_front(); 79 | size++; 80 | CHECK(ring.count() == std::min(size, ring.max_size() - 1)); 81 | } 82 | } 83 | } 84 | 85 | TEST_CASE("ring_buffer construction", "[ring_buffer]"){ 86 | SECTION("default"){ 87 | ring_buffer ring; 88 | CHECK(ring.count() == 0); 89 | CHECK(ring.empty()); 90 | } 91 | 92 | SECTION("initializer_list"){ 93 | ring_buffer ring { 2, 3, 5, 7, 11 }; 94 | CHECK(ring.count() == 5); 95 | auto it = ring.begin(); 96 | CHECK(*it == 2); it++; 97 | CHECK(*it == 3); it++; 98 | CHECK(*it == 5); it++; 99 | CHECK(*it == 7); it++; 100 | CHECK(*it == 11); it++; 101 | CHECK(it == ring.end()); 102 | } 103 | 104 | SECTION("vector"){ 105 | std::vector vec { 2, 3, 5, 7, 11 }; 106 | ring_buffer ring { vec }; 107 | CHECK(ring.count() == 5); 108 | CHECK_THAT(ring, Equals(ring, vec)); 109 | } 110 | 111 | SECTION("copy construction"){ 112 | ring_buffer ring {2, 3, 5, 7, 11}; 113 | ring_buffer ring2 { ring }; 114 | CHECK_THAT(ring, Equals(ring, ring2)); 115 | } 116 | 117 | SECTION("move construction (can compile)"){ 118 | ring_buffer, 4> ring; 119 | ring.emplace_back(new int {42}); 120 | ring.emplace_back(new int {42}); 121 | ring_buffer, 4> ring2 { std::move(ring) }; 122 | } 123 | 124 | SECTION("move construction (correctness)"){ 125 | ring_buffer ring {2, 3, 5, 7, 11}; 126 | ring_buffer ring2 { std::move(ring) }; 127 | CHECK_THAT(ring, Equals(ring, std::vector{2, 3, 5, 7, 11})); 128 | } 129 | } 130 | 131 | TEST_CASE("ring_buffer iterator interface", "[ring_buffer]"){ 132 | SECTION("empty"){ 133 | ring_buffer ring; 134 | CHECK(ring.begin() == ring.end()); 135 | CHECK(std::distance(ring.begin(), ring.end()) == 0); 136 | } 137 | 138 | SECTION("empty const"){ 139 | ring_buffer ring; 140 | CHECK(ring.cbegin() == ring.cend()); 141 | CHECK(std::distance(ring.cbegin(), ring.cend()) == 0); 142 | } 143 | 144 | SECTION("half size"){ 145 | ring_buffer ring; 146 | for (int i=0; i < 4; i++) ring.push_back(i); 147 | CHECK(ring.begin() != ring.end()); 148 | CHECK(std::distance(ring.begin(), ring.end()) == 4); 149 | } 150 | 151 | SECTION("full size"){ 152 | ring_buffer ring; 153 | for (int i=0; i < 8; i++) ring.push_back(i); 154 | CHECK(ring.begin() != ring.end()); 155 | CHECK(std::distance(ring.begin(), ring.end()) == 8); 156 | } 157 | 158 | SECTION("ringed around"){ 159 | ring_buffer ring; 160 | for (int i=0; i < 12; i++) ring.push_back(i); 161 | CHECK(ring.begin() != ring.end()); 162 | CHECK(std::distance(ring.begin(), ring.end()) == 8); 163 | } 164 | 165 | SECTION("ringed around + pop_front"){ 166 | ring_buffer ring; 167 | for (int i=0; i < 12; i++) ring.push_back(i); 168 | for (int i=0; i < 4; i++) ring.pop_front(); 169 | CHECK(ring.begin() != ring.end()); 170 | CHECK(std::distance(ring.begin(), ring.end()) == 4); 171 | } 172 | 173 | SECTION("iterator"){ 174 | ring_buffer ring { 1, 2, 3, 4}; 175 | auto it = ring.begin(); 176 | CHECK(*it == 1); it++; 177 | CHECK(*it == 2); it++; 178 | CHECK(*it == 3); it++; 179 | CHECK(*it == 4); it++; 180 | CHECK(it == ring.end()); 181 | } 182 | 183 | SECTION("const iterator"){ 184 | const ring_buffer ring { 1, 2, 3, 4}; 185 | auto it = ring.begin(); 186 | CHECK(*it == 1); it++; 187 | CHECK(*it == 2); it++; 188 | CHECK(*it == 3); it++; 189 | CHECK(*it == 4); it++; 190 | CHECK(it == ring.end()); 191 | } 192 | 193 | SECTION("reverse iterator"){ 194 | ring_buffer ring { 1, 2, 3, 4}; 195 | auto it = ring.rbegin(); 196 | CHECK(*it == 4); it++; 197 | CHECK(*it == 3); it++; 198 | CHECK(*it == 2); it++; 199 | CHECK(*it == 1); it++; 200 | CHECK(it == ring.rend()); 201 | } 202 | 203 | SECTION("const reverse iterator"){ 204 | const ring_buffer ring { 1, 2, 3, 4}; 205 | auto it = ring.rbegin(); 206 | CHECK(*it == 4); it++; 207 | CHECK(*it == 3); it++; 208 | CHECK(*it == 2); it++; 209 | CHECK(*it == 1); it++; 210 | CHECK(it == ring.rend()); 211 | } 212 | 213 | SECTION("reverse iterator (full)"){ 214 | ring_buffer ring { 1, 2, 3, 4, 5, 6, 7, 8}; 215 | CHECK(ring.rbegin() != ring.rend()); 216 | CHECK(std::distance(ring.rbegin(), ring.rend()) == 8); 217 | } 218 | 219 | SECTION("reverse iterator (overfull)") { 220 | ring_buffer ring { 1, 2, 3, 4, 5, 6, 7, 8 }; 221 | REQUIRE(*ring.begin() == 1); 222 | REQUIRE(*std::prev(ring.end()) == 8); 223 | REQUIRE(ring.start() == 0); 224 | REQUIRE(ring.count() == 8); 225 | ring.push_back(9); 226 | ring.push_back(10); 227 | ring.push_back(11); 228 | ring.push_back(12); 229 | REQUIRE(ring.start() == 4); 230 | REQUIRE(ring.count() == 8); 231 | CHECK(*std::prev(ring.end()) == 12); 232 | CHECK(*ring.rbegin() == 12); 233 | std::vector last_n { ring.rbegin(), std::next(ring.rbegin(), 6) }; 234 | std::vector should_be{ 12, 11, 10, 9, 8, 7 }; 235 | CHECK_THAT(last_n, Equals(should_be)); 236 | } 237 | } 238 | 239 | TEST_CASE("ring_buffer errors", "[ring_buffer]") { 240 | ring_buffer ring; 241 | 242 | SECTION("empty and out of range") { 243 | CHECK_THROWS_AS(ring.at(-1), std::out_of_range); 244 | CHECK_THROWS_AS(ring.at(0), std::out_of_range); 245 | CHECK_THROWS_AS(ring.at(1), std::out_of_range); 246 | CHECK_THROWS_AS(ring.at(8), std::out_of_range); 247 | } 248 | 249 | SECTION("full and out of range") { 250 | for (int i = 0; i < ring.max_size(); ++i) ring.push_back(i); 251 | CHECK_NOTHROW(ring.at(0)); 252 | CHECK_THROWS_AS(ring.at(-1), std::out_of_range); 253 | CHECK_THROWS_AS(ring.at(8), std::out_of_range); 254 | } 255 | } 256 | 257 | TEST_CASE("ring_buffer internals", "[ring_buffer]"){ 258 | SECTION("valid_index 1"){ 259 | ring_buffer ring; 260 | for (int i=0; i<4; i++) ring.push_back(i); 261 | CHECK(ring.valid_index(0)); 262 | CHECK(ring.valid_index(1)); 263 | CHECK(ring.valid_index(2)); 264 | CHECK(ring.valid_index(3)); 265 | CHECK(!ring.valid_index(4)); 266 | CHECK(!ring.valid_index(5)); 267 | CHECK(!ring.valid_index(6)); 268 | CHECK(!ring.valid_index(7)); 269 | } 270 | 271 | SECTION("valid_index 2"){ 272 | ring_buffer ring; 273 | for (int i=0; i<12; i++) ring.push_back(i); 274 | for (int i=0; i<8; i++) CHECK(ring.valid_index(i)); 275 | } 276 | 277 | SECTION("valid_index 3"){ 278 | ring_buffer ring; 279 | ring.push_back(42); 280 | for (int i=0; i<8; i++) { 281 | ring.push_back(42); 282 | ring.pop_front(); 283 | 284 | CHECK(ring.valid_index((i + 1) % ring.max_size())); 285 | for (int j=0; j<8; j++){ 286 | if (j != i) CHECK(!ring.valid_index((j + 1) % ring.max_size())); 287 | } 288 | } 289 | } 290 | } 291 | 292 | TEST_CASE("ring_buffer operator<<", "[ring_buffer]"){ 293 | SECTION("printing"){ 294 | ring_buffer ring { 2, 3, 5, 7, 11 }; 295 | std::cout << "Should print {2, 3, 5, 7, 11, _, _, _}\n"; 296 | std::cout << ring << "\n"; 297 | } 298 | 299 | SECTION("printing"){ 300 | ring_buffer ring; 301 | std::cout << "Should print {}\n"; 302 | std::cout << ring << "\n"; 303 | } 304 | 305 | SECTION("printing"){ 306 | ring_buffer ring { 1, 2, 3, 4, 5, 6, 7, 8 }; 307 | std::cout << "Should print {5, 6, 7, 8}\n"; 308 | std::cout << ring << "\n"; 309 | } 310 | 311 | SECTION("printing"){ 312 | ring_buffer ring { 1, 2, 3, 4 }; 313 | ring.pop_front(); 314 | ring.pop_front(); 315 | std::cout << "Should print {_, _, 3, 4}\n"; 316 | std::cout << ring << "\n"; 317 | } 318 | } -------------------------------------------------------------------------------- /tests/inlined_vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define BSP_INLINED_VECTOR_THROWS 14 | // #define BSP_INLINED_VECTOR_LOG_ERROR(message) std::cerr << message << "\n" 15 | #include "../include/inlined_vector.h" 16 | 17 | #include "catch.hpp" 18 | #include "container_matcher.h" 19 | 20 | using bsp::detail::static_vector; 21 | using bsp::inlined_vector; 22 | 23 | TEST_CASE("inlined_vector basics", "[static_vector]") { 24 | static_vector v; 25 | CHECK(v.size() == 0); 26 | CHECK(v.max_size() == 8); 27 | 28 | v.push_back(42); 29 | v.push_back(3); 30 | 31 | CHECK(*v.begin() == 42); 32 | CHECK(*std::next(v.begin(), 1) == 3); 33 | } 34 | 35 | struct Counter { 36 | int* counter = nullptr; 37 | Counter() = default; 38 | Counter(int* i):counter(i){ if (counter) (*counter)++; } 39 | Counter(const Counter& c){ counter = c.counter; if (counter) (*counter)++; }; 40 | Counter(Counter&& c){ counter = c.counter; c.counter = nullptr; }; 41 | ~Counter(){ if (counter) (*counter)--; } 42 | }; 43 | 44 | TEST_CASE("inlined_vector pod", "[static_vector]") { 45 | 46 | SECTION("destruction"){ 47 | std::cout << "Expect: 1 non-trivial destruct and 1 trivial destruct" << "\n"; 48 | static_vector v1; 49 | static_vector v2; 50 | CHECK(true); 51 | } 52 | 53 | SECTION("more destruction"){ 54 | int counter = 0; 55 | { 56 | static_vector v; 57 | v.emplace_back(&counter); 58 | v.emplace_back(&counter); 59 | v.emplace_back(&counter); 60 | CHECK(counter == 3); 61 | } 62 | CHECK(counter == 0); 63 | 64 | { 65 | static_vector v; 66 | v.emplace_back(&counter); 67 | v.emplace_back(&counter); 68 | v.emplace_back(&counter); 69 | static_vector v2 = v; 70 | CHECK(counter == 6); 71 | } 72 | CHECK(counter == 0); 73 | 74 | { 75 | static_vector v; 76 | v.emplace_back(&counter); 77 | v.emplace_back(&counter); 78 | v.emplace_back(&counter); 79 | static_vector v2 = std::move(v); 80 | CHECK(counter == 3); 81 | } 82 | CHECK(counter == 0); 83 | } 84 | 85 | SECTION("copy"){ 86 | std::cout << "Expect: 1 trivial copy and 1 non-trivial copy" << "\n"; 87 | static_vector v1 (4, 42); 88 | CHECK(v1.size() == 4); 89 | static_vector v2 = v1; 90 | CHECK(v1.size() == 4); 91 | CHECK(v2.size() == 4); 92 | static_vector v3; 93 | v3.push_back(std::string("Hello")); 94 | static_vector v4 = v3; 95 | CHECK(true); 96 | } 97 | 98 | SECTION("copy to self") { 99 | static_vector v1 (4, 42); 100 | v1 = v1; 101 | CHECK(v1.size() == 4); 102 | } 103 | } 104 | 105 | // inlined_vector gNegativeSizedVectorWillStaticAssert; 106 | 107 | TEST_CASE("inlined_vector sizeof", "[inlined_vector]") { 108 | inlined_vector v1; 109 | inlined_vector v2; 110 | CHECK(sizeof(v1) < sizeof(v2)); 111 | } 112 | 113 | TEST_CASE("inlined_vector operator<<", "[inlined_vector]") { 114 | std::cout << "Testing output...\n"; 115 | inlined_vector v1 { 1, 2, 3, 4, 5 }; 116 | inlined_vector v2 { 6, 7, 8, 9, 10 }; 117 | std::cout << v1 << "\n"; 118 | std::cout << v2 << "\n"; 119 | CHECK(true); 120 | } 121 | 122 | TEST_CASE("inlined_vector basic construction", "[inlined_vector]") { 123 | inlined_vector v1; 124 | CHECK(v1.size() == 0); 125 | CHECK(v1.max_size() == 16); 126 | CHECK(!v1.expanded()); 127 | 128 | inlined_vector v2; 129 | CHECK(v2.size() == 0); 130 | CHECK(v2.max_size() == 16); 131 | CHECK(!v2.expanded()); 132 | } 133 | 134 | TEST_CASE("inlined_vector basic operations 1", "[inlined_vector]") { 135 | inlined_vector v1 { 3 }; 136 | v1.push_back(42); 137 | CHECK(v1.size() == 2); 138 | CHECK(v1.front() == 3); 139 | CHECK(v1.back() == 42); 140 | } 141 | 142 | TEST_CASE("inlined_vector basic operations 2", "[inlined_vector]") { 143 | inlined_vector v { 1, 2, 3, 4, 5 }; 144 | REQUIRE(v.max_size() == 16); 145 | REQUIRE(v.size() == 5); 146 | 147 | SECTION("operator[], back, and front works") { 148 | CHECK(v[0] == 1); 149 | CHECK(v[1] == 2); 150 | CHECK(v[2] == 3); 151 | CHECK(v[3] == 4); 152 | CHECK(v[4] == 5); 153 | CHECK(v.front() == 1); 154 | CHECK(v.back() == 5); 155 | } 156 | 157 | SECTION("can push back and pop") { 158 | v.push_back(13); 159 | REQUIRE(v.size() == 6); 160 | REQUIRE(v.back() == 13); 161 | int popResult = v.back(); 162 | v.pop_back(); 163 | CHECK(popResult == 13); 164 | CHECK(v.size() == 5); 165 | } 166 | 167 | SECTION("can erase") { 168 | int valueToErase = 3; 169 | CHECK(v.contains(valueToErase)); 170 | auto it = v.begin(); 171 | while (it != v.end()) { 172 | if (*it == valueToErase) { 173 | it = v.erase(it); 174 | } 175 | else it++; 176 | } 177 | CHECK(!v.contains(valueToErase)); 178 | } 179 | 180 | SECTION("can insert") { 181 | auto it = v.insert(std::next(v.begin(), 3), 42); 182 | CHECK(v.contains(42)); 183 | CHECK(v[3] == 42); 184 | CHECK(v.size() == 6); 185 | v.erase(it); 186 | } 187 | 188 | SECTION("iteration") { 189 | for (auto& p: v){ 190 | p += 1; 191 | } 192 | CHECK(v[4] == 6); 193 | for (auto& p: v){ 194 | p -= 1; 195 | } 196 | 197 | int i = 5; 198 | for (auto it = v.rbegin(); it != v.rend(); ++it){ 199 | CHECK(*it == i); 200 | i--; 201 | } 202 | } 203 | 204 | SECTION("can clear") { 205 | v.clear(); 206 | CHECK(v.empty()); 207 | CHECK(v.size() == 0); 208 | } 209 | } 210 | 211 | TEST_CASE("inlined_vector basic operations 1 (expandable)", "[inlined_vector]") { 212 | inlined_vector v1 { 3 }; 213 | CHECK(v1.front() == 3); 214 | CHECK(v1.size() == 1); 215 | v1.push_back(42); 216 | CHECK(v1.size() == 2); 217 | CHECK(v1.front() == 3); 218 | CHECK(v1.back() == 42); 219 | } 220 | 221 | struct simple_pair { int a, b; }; 222 | TEST_CASE("inlined_vector aggregate push_back", "[inlined_vector]") { 223 | inlined_vector v; 224 | v.push_back({4, 4}); 225 | CHECK(v.size() == 1); 226 | CHECK(v.front().a == 4); 227 | CHECK(v.front().b == 4); 228 | } 229 | 230 | TEST_CASE("inlined_vector basic operations 2 (expandable)", "[inlined_vector]") { 231 | inlined_vector v { 1, 2, 3, 4, 5 }; 232 | REQUIRE(v.max_size() == 16); 233 | REQUIRE(v.size() == 5); 234 | 235 | SECTION("operator[], back, and front works") { 236 | CHECK(v[0] == 1); 237 | CHECK(v[1] == 2); 238 | CHECK(v[2] == 3); 239 | CHECK(v[3] == 4); 240 | CHECK(v[4] == 5); 241 | CHECK(v.front() == 1); 242 | CHECK(v.back() == 5); 243 | } 244 | 245 | SECTION("can push back and pop") { 246 | v.push_back(13); 247 | REQUIRE(v.size() == 6); 248 | REQUIRE(v.back() == 13); 249 | int popResult = v.back(); 250 | v.pop_back(); 251 | CHECK(popResult == 13); 252 | CHECK(v.size() == 5); 253 | } 254 | 255 | SECTION("can push back and pop beyond capacity") { 256 | CHECK(!v.expanded()); 257 | for (int i=0; i<100; i++) v.push_back(i); 258 | CHECK(v.size() == 105); 259 | CHECK(v.expanded()); 260 | for (int i=0; i<100; i++) v.pop_back(); 261 | CHECK(v.size() == 5); 262 | } 263 | 264 | SECTION("can erase") { 265 | int valueToErase = 3; 266 | CHECK(v.contains(valueToErase)); 267 | auto it = v.begin(); 268 | while (it != v.end()) { 269 | if (*it == valueToErase) { 270 | it = v.erase(it); 271 | } 272 | else it++; 273 | } 274 | CHECK(!v.contains(valueToErase)); 275 | } 276 | 277 | SECTION("can insert") { 278 | auto it = v.insert(std::next(v.begin(), 3), 42); 279 | CHECK(v.contains(42)); 280 | CHECK(v[3] == 42); 281 | CHECK(v.size() == 6); 282 | v.erase(it); 283 | } 284 | 285 | SECTION("iteration") { 286 | for (auto& p: v){ 287 | p += 1; 288 | } 289 | CHECK(v[4] == 6); 290 | for (auto& p: v){ 291 | p -= 1; 292 | } 293 | 294 | int i = 5; 295 | for (auto it = v.rbegin(); it != v.rend(); ++it){ 296 | CHECK(*it == i); 297 | i--; 298 | } 299 | } 300 | 301 | SECTION("can clear") { 302 | v.clear(); 303 | CHECK(v.empty()); 304 | CHECK(v.size() == 0); 305 | } 306 | } 307 | 308 | struct MoveOnly { 309 | MoveOnly(int value = 0):value(value){} 310 | MoveOnly(const MoveOnly&) = delete; 311 | MoveOnly(MoveOnly&& other):value(std::move(other.value)){ other.value = 0; } 312 | MoveOnly& operator=(MoveOnly&& other){ value = std::move(other.value); other.value = 0; return *this;} 313 | int value = 0; 314 | }; 315 | 316 | TEST_CASE("inlined_vector construction", "[inlined_vector]"){ 317 | SECTION("construct with duplicates"){ 318 | inlined_vector fv (6, 42); 319 | std::vector result {42, 42, 42, 42, 42, 42 }; 320 | CHECK(fv.size() == 6); 321 | CHECK_THAT(fv, Equals(fv, result)); 322 | } 323 | 324 | SECTION("construct with duplicates"){ 325 | inlined_vector fv (6, 42); 326 | std::vector result {42, 42, 42, 42, 42, 42 }; 327 | CHECK(fv.size() == 6); 328 | CHECK_THAT(fv, Equals(fv, result)); 329 | } 330 | 331 | SECTION("construct with duplicates"){ 332 | inlined_vector fv (100, 42); 333 | CHECK(fv.size() == 100); 334 | for (int i=0; i<100; i++){ 335 | CHECK(fv[i] == 42); 336 | } 337 | } 338 | 339 | SECTION("construct from initialiser_list"){ 340 | inlined_vector fv { 1, 2, 3, 4, 5 }; 341 | std::vector v {1, 2, 3, 4, 5}; 342 | CHECK_THAT(fv, Equals(fv, v)); 343 | } 344 | 345 | SECTION("construct from initialiser_list"){ 346 | inlined_vector fv { 1, 2, 3, 4, 5 }; 347 | std::vector v {1, 2, 3, 4, 5}; 348 | CHECK_THAT(fv, Equals(fv, v)); 349 | } 350 | 351 | SECTION("construct from std::vector"){ 352 | std::vector v {1, 2, 3, 4, 5}; 353 | inlined_vector fv = v; 354 | CHECK_THAT(fv, Equals(fv, v)); 355 | } 356 | 357 | SECTION("construct from std::vector"){ 358 | std::vector v {1, 2, 3, 4, 5}; 359 | inlined_vector fv = v; 360 | CHECK_THAT(fv, Equals(fv, v)); 361 | } 362 | 363 | SECTION("copy construct"){ 364 | inlined_vector v1 { 1, 2, 3, 4, 5 }; 365 | inlined_vector v2 { v1 }; 366 | CHECK_THAT(v1, Equals(v1, v2)); 367 | } 368 | 369 | SECTION("copy construct"){ 370 | inlined_vector v1 { 1, 2, 3, 4, 5 }; 371 | inlined_vector v2 { v1 }; 372 | CHECK_THAT(v1, Equals(v1, v2)); 373 | } 374 | 375 | SECTION("move construct"){ 376 | auto res = { 1, 2, 3, 4, 5 }; 377 | inlined_vector v1 { res }; 378 | inlined_vector v2 { std::move(v1) }; 379 | CHECK_THAT(v2, Equals(v2, res)); 380 | } 381 | 382 | SECTION("move construct"){ 383 | auto res = { 1, 2, 3, 4, 5 }; 384 | inlined_vector v1 { res }; 385 | inlined_vector v2 { std::move(v1) }; 386 | CHECK_THAT(v2, Equals(v2, res)); 387 | } 388 | 389 | SECTION("move construct") { 390 | auto res = { 1, 2, 3, 4, 5 }; 391 | inlined_vector v1{ res }; 392 | inlined_vector v2{ std::move(v1) }; 393 | CHECK_THAT(v2, Equals(v2, res)); 394 | } 395 | 396 | SECTION("move construct"){ 397 | inlined_vector v1; 398 | v1.emplace_back(); 399 | v1.emplace_back(); 400 | v1.emplace_back(); 401 | v1.emplace_back(); 402 | 403 | inlined_vector v2 { std::move(v1) }; 404 | CHECK(v2.size() == 4); 405 | } 406 | 407 | SECTION("move construct"){ 408 | inlined_vector v1; 409 | v1.emplace_back(); 410 | v1.emplace_back(); 411 | v1.emplace_back(); 412 | v1.emplace_back(); 413 | 414 | inlined_vector v2 { std::move(v1) }; 415 | CHECK(v2.size() == 4); 416 | } 417 | 418 | SECTION("copy assignment"){ 419 | auto res = { 1, 2, 3, 4, 5 }; 420 | inlined_vector v1 { res }; 421 | inlined_vector v2; 422 | v2 = v1; 423 | CHECK_THAT(v2, Equals(v2, res)); 424 | } 425 | 426 | SECTION("copy assignment"){ 427 | auto res = { 1, 2, 3, 4, 5 }; 428 | inlined_vector v1 { res }; 429 | inlined_vector v2; 430 | v2 = v1; 431 | CHECK_THAT(v2, Equals(v2, res)); 432 | } 433 | 434 | SECTION("move assignment"){ 435 | auto res = { 1, 2, 3, 4, 5 }; 436 | inlined_vector v1 { res }; 437 | inlined_vector v2; 438 | v2 = std::move(v1); 439 | CHECK_THAT(v2, Equals(v2, res)); 440 | } 441 | 442 | SECTION("move assignment"){ 443 | auto res = { 1, 2, 3, 4, 5 }; 444 | inlined_vector v1 { res }; 445 | inlined_vector v2; 446 | v2 = std::move(v1); 447 | CHECK_THAT(v2, Equals(v2, res)); 448 | } 449 | 450 | SECTION("move assignment"){ 451 | auto res = { 1, 2, 3, 4, 5 }; 452 | inlined_vector v1 { res }; 453 | inlined_vector v2; 454 | v2 = std::move(v1); 455 | CHECK_THAT(v2, Equals(v2, res)); 456 | } 457 | 458 | SECTION("extend"){ 459 | auto res1 = { 1, 2, 3, 4, 5 }; 460 | auto res2 = { 1, 2, 3, 4, 5 }; 461 | inlined_vector v1 { res1 }; 462 | v1.extend(res2); 463 | CHECK(v1.size() == 10); 464 | } 465 | } 466 | 467 | struct EmplaceableStruct { 468 | int a = 0, b = 0, c = 0; 469 | EmplaceableStruct() = default; 470 | EmplaceableStruct(int a, int b, int c):a{a},b{b},c{c}{} 471 | }; 472 | 473 | TEST_CASE("inlined_vector emplacement", "[inlined_vector]") { 474 | SECTION("vector"){ 475 | inlined_vector, 2, false> v; 476 | for (int i=0; i<2; ++i) v.emplace_back(new int {i}); 477 | CHECK(*v.back() == 1); 478 | } 479 | 480 | SECTION("vector (expandable)"){ 481 | inlined_vector, 2, true> v; 482 | for (int i=0; i<10; ++i) v.emplace_back(new int {i}); 483 | CHECK(*v.back() == 9); 484 | } 485 | 486 | SECTION("many params"){ 487 | inlined_vector v; 488 | for (int i=0; i<2; ++i) v.emplace_back(0, i, 1 + i); 489 | } 490 | 491 | SECTION("many params (expandable)"){ 492 | inlined_vector v; 493 | for (int i=0; i<10; ++i) v.emplace_back(0, i, 1 + i); 494 | } 495 | } 496 | 497 | #ifdef BSP_INLINED_VECTOR_THROWS 498 | TEST_CASE("inlined_vector exception reporting", "[inlined_vector]"){ 499 | SECTION ("too many elements in std::vector"){ 500 | std::vector v (100, 0); 501 | CHECK_THROWS(inlined_vector(v)); //, std::exception); 502 | } 503 | 504 | SECTION ("too many elements in initializer_list"){ 505 | CHECK_THROWS(inlined_vector {1, 2, 3, 4, 5, 6, 7, 8, 9}); //, std::exception); 506 | } 507 | 508 | SECTION ("too many elements in copy constructor"){ 509 | inlined_vector v1 { 1, 2, 3, 4}; 510 | CHECK_THROWS(inlined_vector {v1}); 511 | } 512 | 513 | SECTION ("too many elements in copy constructor"){ 514 | inlined_vector v1 { 1, 2, 3, 4}; 515 | CHECK_THROWS(inlined_vector {v1}); 516 | } 517 | 518 | SECTION ("too many elements in push_back"){ 519 | inlined_vector v1 { 1, 2, 3 }; 520 | CHECK_NOTHROW(v1.push_back(42)); 521 | CHECK_THROWS(v1.push_back(666)); 522 | } 523 | } 524 | #endif 525 | 526 | #ifndef BSP_INLINED_VECTOR_THROWS 527 | TEST_CASE("inlined_vector ignore extra elements", "[inlined_vector]"){ 528 | SECTION ("too many elements in std::vector"){ 529 | std::vector v (100, 42); 530 | v[7] = 1; 531 | inlined_vector v2 = v; 532 | CHECK(v2.front() == 42); 533 | CHECK(v2.back() == 1); 534 | } 535 | } 536 | #endif 537 | 538 | TEST_CASE("inlined_vector moveability", "[inlined_vector]"){ 539 | SECTION("can move element into vector"){ 540 | inlined_vector v; 541 | MoveOnly p {3}; 542 | v.push_back(std::move(p)); 543 | auto& q = v.back(); 544 | q.value = 6; 545 | CHECK(v.back().value == 6); 546 | } 547 | 548 | SECTION("can move vector"){ 549 | inlined_vector v1; 550 | MoveOnly p {3}; 551 | v1.push_back(std::move(p)); 552 | inlined_vector v2 = std::move(v1); 553 | CHECK(v2.front().value == 3); 554 | } 555 | } 556 | 557 | TEST_CASE("inlined_vector moveability (expanded)", "[inlined_vector]"){ 558 | SECTION("can move element into vector"){ 559 | inlined_vector v; 560 | MoveOnly p {3}; 561 | v.push_back(std::move(p)); 562 | auto& q = v.back(); 563 | q.value = 6; 564 | CHECK(v.back().value == 6); 565 | } 566 | 567 | SECTION("can move vector"){ 568 | inlined_vector v1; 569 | MoveOnly p {3}; 570 | v1.push_back(std::move(p)); 571 | inlined_vector v2 = std::move(v1); 572 | CHECK(v2.front().value == 3); 573 | } 574 | 575 | SECTION("can expand vector with moveable elements"){ 576 | inlined_vector v; 577 | v.emplace_back(0); 578 | for (int i=0; i<10; i++){ 579 | v.push_back(MoveOnly{42}); 580 | } 581 | v.emplace_back(42); 582 | CHECK(v.back().value == 42); 583 | } 584 | } 585 | 586 | TEST_CASE("inlined_vector assignment", "[inlined_vector]"){ 587 | inlined_vector v1 { 1, 2, 3, 4 }; 588 | inlined_vector v2 { 1, 2, 4, 8 }; 589 | inlined_vector v3 { 0, 1, 0, 1, 0, 1, 0, 1}; 590 | inlined_vector v4 { 42, 42, 42, 42, 42, 42, 42, 42 }; 591 | 592 | v1 = v2; 593 | CHECK_THAT(v1, Equals(v1, v2)); 594 | 595 | v2 = v3; 596 | CHECK_THAT(v2, Equals(v2, v3)); 597 | 598 | v3 = v4; 599 | CHECK_THAT(v3, Equals(v3, v4)); 600 | } 601 | 602 | using Clock = std::chrono::high_resolution_clock; 603 | struct Profile { 604 | decltype(Clock::now()) start = Clock::now(); 605 | ~Profile(){ 606 | auto end = Clock::now(); 607 | std::cout << "- result: " << std::chrono::duration_cast(end - start).count() << "ns\n"; 608 | } 609 | }; 610 | 611 | TEST_CASE("inlined_vector benchmark", "[inlined_vector]"){ 612 | std::cout << "Performing basic benchmarks\n"; 613 | 614 | constexpr int ArraySize = 128; 615 | constexpr int VecSize = 128; 616 | 617 | { 618 | std::cout << "inlined_vector\n"; 619 | Profile profiler; 620 | 621 | std::array, ArraySize> vecs; 622 | for (auto& vec: vecs){ 623 | for (int i=0; i, ArraySize> vecs; 634 | for (auto& vec: vecs){ 635 | for (int i=0; i, ArraySize> vecs; 646 | for (auto& vec: vecs){ 647 | for (int i=0; i; 656 | vector v1 {1, 2, 3, 4, 5}; 657 | vector v2 {1, 2, 3}; 658 | CHECK(v1.size() == 5); 659 | CHECK(v2.size() == 3); 660 | v2 = std::move(v1); 661 | 662 | // in-place move 663 | std::aligned_storage::type storage; 664 | new (&storage) vector(std::move(v2)); 665 | vector* v3 = reinterpret_cast(&storage); 666 | 667 | CHECK(v2.size() == 0); 668 | CHECK(v3->size() == 5); 669 | 670 | v3->~vector(); 671 | } 672 | -------------------------------------------------------------------------------- /include/inlined_vector.h: -------------------------------------------------------------------------------- 1 | // Customise the behaviour of inlined_vector by defining these before including it: 2 | // #define BSP_INLINED_VECTOR_THROWS to get runtime_error 3 | // #define BSP_INLINED_VECTOR_LOG_ERROR(message) to log errors 4 | 5 | #ifndef BSP_INLINED_VECTOR_H 6 | #define BSP_INLINED_VECTOR_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef BSP_INLINED_VECTOR_THROWS 17 | #include 18 | #endif 19 | 20 | namespace bsp { 21 | namespace detail { 22 | template class static_vector { 23 | static_assert(Capacity > 0, "Capacity is <= 0!"); 24 | 25 | public: 26 | using value_type = T; 27 | using iterator = value_type*; 28 | using const_iterator = const value_type*; 29 | using reverse_iterator = std::reverse_iterator; 30 | using const_reverse_iterator = std::reverse_iterator; 31 | using size_type = int; 32 | 33 | public: 34 | static_vector() = default; 35 | 36 | explicit static_vector(size_type count, const T& value = T()){ 37 | size_ = count; 38 | for(size_type i = 0; i < size_; ++i) { 39 | new (data_+i) T(value); 40 | } 41 | } 42 | 43 | static_vector(const static_vector& other){ 44 | size_ = other.size_; 45 | for(size_type i = 0; i < size_; ++i) { 46 | new (data_+i) T(other[i]); 47 | } 48 | } 49 | 50 | static_vector(static_vector&& other) noexcept { 51 | size_ = other.size_; 52 | for(size_type i = 0; i < size_; ++i) { 53 | new (data_+i) T(std::move(other[i])); 54 | } 55 | other.clear(); 56 | } 57 | 58 | static_vector& operator=(const static_vector& other){ 59 | if (other.data_ == data_) return *this; 60 | 61 | destroy_all(); 62 | size_ = other.size_; 63 | for(size_type i = 0; i < size_; ++i) { 64 | new (data_+i) T(other[i]); 65 | } 66 | return *this; 67 | } 68 | 69 | static_vector& operator=(static_vector&& other) noexcept { 70 | if (other.data_ == data_) return *this; 71 | 72 | destroy_all(); 73 | size_ = other.size_; 74 | for(size_type i = 0; i < size_; ++i) { 75 | new (data_+i) T(std::move(other[i])); 76 | } 77 | other.clear(); 78 | return *this; 79 | } 80 | 81 | ~static_vector(){ 82 | destroy_all(); 83 | } 84 | 85 | size_type size() const { return size_; } 86 | 87 | constexpr static size_type max_size() { return Capacity; } 88 | 89 | void clear(){ 90 | destroy_all(); 91 | } 92 | 93 | template 94 | void push_back(U&& value) { 95 | if( size_ >= max_size() ) throw std::bad_alloc{}; 96 | new (data_+size_) T(std::forward(value)); 97 | ++size_; 98 | } 99 | 100 | template void emplace_back(Args&&... args) { 101 | if( size_ >= max_size() ) throw std::bad_alloc{}; 102 | new (data_+size_) T(std::forward(args)...); 103 | ++size_; 104 | } 105 | 106 | void pop_back(){ 107 | assert(size_ > 0); 108 | destroy(data_ + size_); 109 | --size_; 110 | } 111 | 112 | T& operator[](size_type i){ 113 | return *launder(data_ + i); 114 | } 115 | 116 | const T& operator[](size_type i) const { 117 | return *launder(data_ + i); 118 | } 119 | 120 | iterator begin() { return launder(data_); } 121 | iterator end() { return begin() + size_; } 122 | 123 | const_iterator cbegin() const { return begin(); } 124 | const_iterator cend() const { return end(); } 125 | 126 | const_iterator begin() const { return launder(data_); } 127 | const_iterator end() const { return begin() + size_; } 128 | 129 | reverse_iterator rbegin() { return rend() - size_; } 130 | reverse_iterator rend() { return std::reverse_iterator(begin()); } 131 | 132 | const_reverse_iterator rbegin() const { return rend() - size_; } 133 | const_reverse_iterator rend() const { return std::reverse_iterator(begin()); } 134 | 135 | template void emplace_into(Container& container){ 136 | assert(container.size() == 0); 137 | container.resize(size_); 138 | std::move(begin(), end(), container.begin()); 139 | destroy_all(); 140 | } 141 | 142 | void fill_n(size_type count, const T& value) { 143 | destroy_all(); 144 | if( count > max_size() ) throw std::bad_alloc{}; 145 | for(size_type i = 0; i < count; ++i) { 146 | new (data_+i) T(value); 147 | } 148 | size_ = count; 149 | } 150 | 151 | protected: 152 | using raw_type = typename std::aligned_storage::type; 153 | 154 | raw_type data_[Capacity]; 155 | size_type size_ = 0; 156 | 157 | protected: 158 | T* launder(raw_type* rt){ 159 | return reinterpret_cast(rt); 160 | } 161 | 162 | const T* launder(const raw_type* rt) const { 163 | return reinterpret_cast(rt); 164 | } 165 | 166 | void destroy(raw_type* rt){ 167 | launder(rt)->~T(); 168 | } 169 | 170 | void destroy_all(){ 171 | for(size_type i = 0; i < size_; ++i) { 172 | destroy(data_+i); 173 | } 174 | size_ = 0; 175 | } 176 | }; 177 | 178 | template struct is_iterator : std::false_type {}; 179 | template struct is_iterator::iterator_category>::value || 181 | std::is_same::iterator_category>::value>::type>: std::true_type {}; 182 | 183 | template< bool B, class T = void > 184 | using enable_if_t = typename std::enable_if::type; 185 | 186 | template 187 | using initializer_list_of_copyable = 188 | enable_if_t::value, std::initializer_list>; 189 | 190 | template 191 | using const_ref_if_copyable = 192 | enable_if_t::value, const T_&>; 193 | } 194 | 195 | // An inlined_vector is a fixed-size array with a vector-like interface 196 | // that can optionally grow beyond its capacity and become a std::vector. 197 | template 198 | class inlined_vector { 199 | static_assert(Capacity > 0, "Capacity is <= 0!"); 200 | 201 | public: 202 | using value_type = T; 203 | using reference = T&; 204 | using const_reference = const T&; 205 | using iterator = value_type*; 206 | using const_iterator = const value_type*; 207 | using reverse_iterator = std::reverse_iterator; 208 | using const_reverse_iterator = std::reverse_iterator; 209 | using size_type = int; 210 | 211 | public: 212 | inlined_vector() = default; 213 | 214 | inlined_vector(size_type count, const T& value = T()):data_internal_(std::min(count, max_size()), value){ 215 | if (count > max_size()) { 216 | size_ = max_size(); 217 | length_error("inlined_vector(count, value) got too many elements"); 218 | } 219 | else { 220 | size_ = count; 221 | } 222 | } 223 | 224 | template 225 | inlined_vector(const inlined_vector& other) 226 | : inlined_vector(other.begin(), other.size()) { 227 | inlined_vector::assert_integrity(); 228 | other.assert_integrity(); 229 | } 230 | 231 | template 232 | inlined_vector(inlined_vector&& other) 233 | : inlined_vector(other.begin(), other.size()) { 234 | inlined_vector::assert_integrity(); 235 | other.assert_integrity(); 236 | } 237 | 238 | template 239 | inlined_vector(const Container& els) : inlined_vector(els.begin(), static_cast(els.size())) { 240 | inlined_vector::assert_integrity(); 241 | } 242 | 243 | inlined_vector(std::initializer_list els) : inlined_vector(els.begin(), static_cast(els.size())) { 244 | inlined_vector::assert_integrity(); 245 | } 246 | 247 | virtual ~inlined_vector() = default; 248 | 249 | constexpr static size_type max_size() { return Capacity; } 250 | 251 | virtual bool can_expand() const { return false; } 252 | 253 | virtual void clear() { 254 | size_ = 0; 255 | data_internal_.clear(); 256 | this->assert_integrity(); 257 | } 258 | 259 | size_type size() const { return size_; } 260 | 261 | bool empty() const { return size_ == 0; } 262 | 263 | bool full() const { return size_ >= max_size(); } 264 | 265 | virtual bool expanded() const { return false; } 266 | 267 | template 268 | void push_back(detail::const_ref_if_copyable value) { 269 | // NB: Need this to support aggregates 270 | if (size_ >= max_size()) { 271 | length_error("inlined_vector::push_back exceeded capacity"); 272 | } 273 | else { 274 | data_internal_.push_back(value); 275 | size_++; 276 | this->assert_integrity(); 277 | } 278 | } 279 | 280 | template 281 | void push_back(U&& value) { 282 | if (size_ >= max_size()) { 283 | length_error("inlined_vector::push_back exceeded capacity"); 284 | } 285 | else { 286 | data_internal_.push_back(std::forward(value)); 287 | size_++; 288 | this->assert_integrity(); 289 | } 290 | } 291 | 292 | template void emplace_back(Args&&... args) { 293 | if (size_ >= max_size()) { 294 | length_error("inlined_vector::emplace_back exceeded capacity"); 295 | } 296 | else { 297 | data_internal_.emplace_back(std::forward(args)...); 298 | size_++; 299 | this->assert_integrity(); 300 | } 301 | } 302 | 303 | template void extend(const Container& other) { 304 | for (auto v : other) { 305 | push_back(std::move(v)); 306 | } 307 | this->assert_integrity(); 308 | } 309 | 310 | template 311 | void extend(detail::initializer_list_of_copyable other) { 312 | for (auto v : other) { 313 | push_back(std::move(v)); 314 | } 315 | this->assert_integrity(); 316 | } 317 | 318 | virtual void pop_back() { 319 | if (!empty()){ 320 | data_internal_.pop_back(); 321 | size_--; 322 | this->assert_integrity(); 323 | } 324 | } 325 | 326 | const_reference back() const { 327 | if (!empty()) { 328 | return *std::prev(end()); 329 | } 330 | return data_internal_[0]; 331 | } 332 | 333 | reference back() { return const_cast(static_cast(this)->back()); } 334 | 335 | const_reference front() const { 336 | if (!empty()) { 337 | return *begin(); 338 | } 339 | return data_internal_[0]; 340 | } 341 | 342 | reference front() { return const_cast(static_cast(this)->front()); } 343 | 344 | reference operator[](size_type i) { return element(i); } 345 | 346 | const_reference operator[](size_type i) const { return element(i); } 347 | 348 | const_reference at(size_type i) const { 349 | if (i >= 0 && i < size_) { 350 | return element(i); 351 | } 352 | else { 353 | throw std::out_of_range("inlined_vector::at"); 354 | } 355 | } 356 | 357 | reference at(size_type i) { 358 | return const_cast(static_cast(this)->at(i)); 359 | } 360 | 361 | virtual iterator begin() { return data_internal_.begin(); } 362 | iterator end() { return begin() + size_; } 363 | 364 | const_iterator cbegin() const { return begin(); } 365 | const_iterator cend() const { return end(); } 366 | 367 | virtual const_iterator begin() const { return data_internal_.begin(); } 368 | const_iterator end() const { return begin() + size_; } 369 | 370 | reverse_iterator rbegin() { return rend() - size_; } 371 | virtual reverse_iterator rend() { return data_internal_.rend(); } 372 | 373 | const_reverse_iterator rbegin() const { return rend() - size_; } 374 | virtual const_reverse_iterator rend() const { return data_internal_.rend(); } 375 | 376 | virtual iterator erase(const_iterator it) { 377 | validate_iterator(it); 378 | 379 | if (it == end() || empty()) { 380 | out_of_range_error("inlined_vector::erase it == end or container is empty"); 381 | return end(); 382 | } 383 | 384 | size_type i = iterator_index(it); 385 | if (i == size_) { 386 | out_of_range_error("inlined_vector::insert invalid iterator"); 387 | return end(); 388 | } 389 | for (size_type j = i; j < size_ - 1; ++j) { 390 | element(j) = std::move(element(j + 1)); 391 | } 392 | data_internal_.pop_back(); 393 | size_--; 394 | this->assert_integrity(); 395 | return begin() + i; 396 | } 397 | 398 | template 399 | iterator insert(iterator it, detail::const_ref_if_copyable value) { 400 | validate_iterator(it); 401 | 402 | if (full()) { 403 | length_error("inlined_vector::insert exceeded Capacity"); 404 | return end(); 405 | } 406 | 407 | if (it == end()) { 408 | push_back(value); 409 | this->assert_integrity(); 410 | return std::prev(end(), 1); 411 | } 412 | else { 413 | // Insert at i and push everything back 414 | size_type i = iterator_index(it); 415 | if (i == size_) { 416 | out_of_range_error("inlined_vector::insert invalid iterator"); 417 | return end(); 418 | } 419 | data_internal_.push_back(std::move(element(size_ - 1))); 420 | for (size_type j = size_ - 1; j > i; j--) { 421 | element(j) = std::move(element(j - 1)); 422 | } 423 | element(i) = value; 424 | size_++; 425 | this->assert_integrity(); 426 | return std::next(begin(), i); 427 | } 428 | } 429 | 430 | bool contains(const_reference value) const { 431 | auto begin_ = begin(); 432 | auto end_ = end(); 433 | return std::find(begin_, end_, value) != end_; 434 | } 435 | 436 | protected: 437 | using array_type = detail::static_vector; 438 | 439 | array_type data_internal_; 440 | size_type size_ = 0; 441 | 442 | protected: 443 | // Helper constructor 444 | template::value>::type> 445 | inlined_vector(Iter begin_, int size) { 446 | if (size > max_size()) { 447 | length_error("inlined_vector() too many elements"); 448 | size_ = max_size(); 449 | } 450 | else { 451 | size_ = size; 452 | } 453 | 454 | auto end_ = std::next(begin_, size_); 455 | for (auto it = begin_; it != end_; ++it){ 456 | data_internal_.emplace_back(std::move(*it)); 457 | } 458 | 459 | inlined_vector::assert_integrity(); 460 | } 461 | 462 | // Helper constructor for sub-class 463 | inlined_vector(array_type&& array, int size, bool inlined) 464 | : data_internal_(std::move(array)), size_(size) { 465 | assert(array.size() == 0); 466 | assert(!inlined || size_ == data_internal_.size()); 467 | } 468 | 469 | reference element(size_type index) { return *std::next(begin(), index); } 470 | 471 | const_reference element(size_type index) const { return *std::next(begin(), index); } 472 | 473 | size_type iterator_index(const_iterator it) const { 474 | auto nit = begin(); 475 | for (size_type i = 0; i < size_; i++) { 476 | if (nit == it) 477 | return i; 478 | ++nit; 479 | } 480 | return size_; 481 | } 482 | 483 | void validate_iterator(const_iterator it) { 484 | #ifndef NDEBUG 485 | if (it < begin() || it > end()) { 486 | out_of_range_error("inlined_vector::validate_iterator invalid iterator"); 487 | } 488 | #endif 489 | } 490 | 491 | void length_error(const char* message) const { 492 | #ifdef BSP_INLINED_VECTOR_LOG_ERROR 493 | BSP_INLINED_VECTOR_LOG_ERROR(message); 494 | #endif 495 | 496 | #ifdef BSP_INLINED_VECTOR_THROWS 497 | throw std::length_error(message); 498 | #endif 499 | } 500 | 501 | void out_of_range_error(const char* message) const { 502 | #ifdef BSP_INLINED_VECTOR_LOG_ERROR 503 | BSP_INLINED_VECTOR_LOG_ERROR(message); 504 | #endif 505 | 506 | #ifdef BSP_INLINED_VECTOR_THROWS 507 | throw std::out_of_range(message); 508 | #endif 509 | } 510 | 511 | public: 512 | virtual void assert_integrity() const { 513 | #ifndef NDEBUG 514 | assert(size_ == data_internal_.size()); 515 | #endif 516 | } 517 | 518 | template 519 | friend std::ostream& operator<<(std::ostream& out, const inlined_vector& vector); 520 | }; 521 | 522 | template 523 | class inlined_vector : public inlined_vector { 524 | static_assert(Capacity > 0, "Capacity is <= 0!"); 525 | 526 | public: 527 | using base_t = inlined_vector; 528 | using typename base_t::value_type; 529 | using typename base_t::reference; 530 | using typename base_t::const_reference; 531 | using typename base_t::iterator; 532 | using typename base_t::const_iterator; 533 | using typename base_t::reverse_iterator; 534 | using typename base_t::const_reverse_iterator; 535 | using typename base_t::size_type; 536 | using base_t::assert_integrity; 537 | using base_t::cbegin; 538 | using base_t::element; 539 | using base_t::empty; 540 | using base_t::end; 541 | using base_t::out_of_range_error; 542 | using base_t::length_error; 543 | using base_t::max_size; 544 | using base_t::rbegin; 545 | using base_t::data_internal_; 546 | using base_t::size_; 547 | 548 | public: 549 | inlined_vector() = default; 550 | 551 | inlined_vector(size_type count, const T& value = T()){ 552 | size_ = count; 553 | if (size_ <= max_size()){ 554 | data_internal_.fill_n(count, value); 555 | } 556 | else { 557 | data_external_.resize(size_); 558 | std::fill_n(data_external_.begin(), count, value); 559 | inlined_ = false; 560 | } 561 | 562 | this->assert_integrity(); 563 | } 564 | 565 | template 566 | inlined_vector(const inlined_vector& other) 567 | : inlined_vector(other.begin(), other.end(), other.size()) { 568 | other.assert_integrity(); 569 | this->assert_integrity(); 570 | } 571 | 572 | inlined_vector(inlined_vector&& other) noexcept 573 | : base_t(std::move(other.data_internal_), other.size_, other.inlined_), 574 | data_external_(std::move(other.data_external_)), 575 | inlined_(other.inlined_) { 576 | other.inlined_ = true; 577 | other.size_ = 0; 578 | 579 | other.assert_integrity(); 580 | this->assert_integrity(); 581 | } 582 | 583 | template 584 | inlined_vector(const Container& els) : inlined_vector(els.begin(), els.end(), static_cast(els.size())) { 585 | this->assert_integrity(); 586 | } 587 | 588 | inlined_vector(std::initializer_list els) 589 | : inlined_vector(els.begin(), els.end(), static_cast(els.size())) { 590 | this->assert_integrity(); 591 | } 592 | 593 | inlined_vector(const inlined_vector& other) 594 | : inlined_vector(other.begin(), other.end(), other.size()) { 595 | this->assert_integrity(); 596 | } 597 | 598 | inlined_vector& operator=(inlined_vector&& other) noexcept { 599 | inlined_ = other.inlined_; 600 | size_ = other.size_; 601 | data_internal_ = std::move(other.data_internal_); 602 | data_external_ = std::move(other.data_external_); 603 | other.inlined_ = true; 604 | other.size_ = 0; 605 | other.assert_integrity(); 606 | this->assert_integrity(); 607 | return *this; 608 | } 609 | 610 | inlined_vector& operator=(const inlined_vector& other) { 611 | inlined_ = other.inlined_; 612 | size_ = other.size_; 613 | data_internal_ = other.data_internal_; 614 | data_external_ = other.data_external_; 615 | other.assert_integrity(); 616 | this->assert_integrity(); 617 | return *this; 618 | } 619 | 620 | template void extend(const Container& other) { 621 | for (auto v : other) { 622 | push_back(std::move(v)); 623 | } 624 | this->assert_integrity(); 625 | } 626 | 627 | template 628 | void extend(detail::initializer_list_of_copyable other) { 629 | for (auto v : other) { 630 | push_back(std::move(v)); 631 | } 632 | this->assert_integrity(); 633 | } 634 | 635 | bool can_expand() const override final { return true; } 636 | 637 | void clear() override final { 638 | if (inlined_) { 639 | base_t::clear(); 640 | } 641 | else if (!inlined_) { 642 | inlined_ = true; 643 | size_ = 0; 644 | data_external_.clear(); 645 | this->assert_integrity(); 646 | } 647 | } 648 | 649 | bool expanded() const final override { return !inlined_; } 650 | 651 | template 652 | void push_back(detail::const_ref_if_copyable value) { 653 | // NB: Need this overload to support aggregates in the parameter 654 | if (inlined_ && size_ >= max_size()) { 655 | grow_to_external_storage(); 656 | } 657 | 658 | if (inlined_) { 659 | base_t::push_back(value); 660 | } 661 | else { 662 | data_external_.push_back(value); 663 | ++size_; 664 | } 665 | 666 | this->assert_integrity(); 667 | } 668 | 669 | template 670 | void push_back(U&& value) { 671 | if (inlined_ && size_ >= max_size()) { 672 | grow_to_external_storage(); 673 | } 674 | 675 | if (inlined_) { 676 | base_t::push_back(std::forward(value)); 677 | } 678 | else { 679 | data_external_.push_back(std::forward(value)); 680 | ++size_; 681 | } 682 | 683 | this->assert_integrity(); 684 | } 685 | 686 | template void emplace_back(Args&&... args) { 687 | if (inlined_ && size_ >= max_size()) { 688 | grow_to_external_storage(); 689 | } 690 | 691 | if (inlined_) { 692 | base_t::emplace_back(std::forward(args)...); 693 | } 694 | else { 695 | data_external_.emplace_back(std::forward(args)...); 696 | ++size_; 697 | } 698 | 699 | this->assert_integrity(); 700 | } 701 | 702 | void pop_back() override final { 703 | if (!empty()){ 704 | if (inlined_){ 705 | base_t::pop_back(); 706 | } 707 | else { 708 | // TODO: become inlined again if small enough? 709 | data_external_.pop_back(); 710 | --size_; 711 | } 712 | 713 | this->assert_integrity(); 714 | } 715 | } 716 | 717 | iterator begin() override final { 718 | return inlined_ ? data_internal_.begin() : unwrap(data_external_.begin()); 719 | } 720 | const_iterator begin() const override final { 721 | return inlined_ ? data_internal_.begin() : unwrap(data_external_.begin()); 722 | } 723 | reverse_iterator rend() override final { 724 | return inlined_ ? data_internal_.rend() : unwrap(data_external_.rend()); 725 | } 726 | const_reverse_iterator rend() const override final { 727 | return inlined_ ? data_internal_.rend() : unwrap(data_external_.rend()); 728 | } 729 | 730 | iterator erase(const_iterator it) override final { 731 | base_t::validate_iterator(it); 732 | 733 | if (it == end() || empty()) { 734 | out_of_range_error("inlined_vector::erase it == end or container is empty"); 735 | } 736 | 737 | if (inlined_) { 738 | size_type i = base_t::iterator_index(it); 739 | if (i == size_) { 740 | out_of_range_error("inlined_vector::erase invalid iterator"); 741 | return end(); 742 | } 743 | for (size_type j = i; j < size_ - 1; ++j) { 744 | element(j) = std::move(element(j + 1)); 745 | } 746 | data_internal_.pop_back(); 747 | --size_; 748 | this->assert_integrity(); 749 | return begin() + i; 750 | } 751 | else { 752 | --size_; 753 | // Note: a bug in gcc 4.8.4 means we have to use a non-const iterator here 754 | auto vit = std::next(data_external_.begin(), std::distance(cbegin(), it)); 755 | auto res = unwrap(data_external_.erase(vit)); 756 | this->assert_integrity(); 757 | return res; 758 | } 759 | } 760 | 761 | template 762 | iterator insert(iterator it, detail::const_ref_if_copyable value) { 763 | base_t::validate_iterator(it); 764 | 765 | if (inlined_ && size_ < max_size()) { 766 | return base_t::insert(it, value); 767 | } 768 | else if (inlined_ && size_ >= max_size()) { 769 | size_type index_ = base_t::iterator_index(it); 770 | grow_to_external_storage(); 771 | it = std::next(begin(), index_); 772 | } 773 | 774 | if (it == end()) { 775 | push_back(value); 776 | this->assert_integrity(); 777 | return end(); 778 | } 779 | else { 780 | ++size_; 781 | // NB: dataVector may not have a T* iterator 782 | auto vit = std::next(data_external_.begin(), std::distance(begin(), it)); 783 | auto res = unwrap(data_external_.insert(vit, value)); 784 | this->assert_integrity(); 785 | return res; 786 | } 787 | } 788 | 789 | protected: 790 | std::vector data_external_; 791 | bool inlined_ = true; 792 | 793 | protected: 794 | // Helper constructor 795 | template inlined_vector(Iter begin_, Iter end_, size_type size) { 796 | size_ = size; 797 | if (size_ <= max_size()) { 798 | for (auto it = begin_; it != end_; ++it){ 799 | data_internal_.emplace_back(std::move(*it)); 800 | } 801 | } 802 | else { 803 | data_external_.resize(size_); 804 | std::move(begin_, end_, data_external_.begin()); 805 | inlined_ = false; 806 | } 807 | 808 | this->assert_integrity(); 809 | } 810 | 811 | // Sometimes std::vector::iterator isn't a T* (e.g., in clang/libcxx) 812 | // so we need to unwrap the iterator 813 | iterator unwrap(typename std::vector::iterator it) const { return &*it; } 814 | const_iterator unwrap(typename std::vector::const_iterator it) const { return &*it; } 815 | reverse_iterator unwrap(typename std::vector::reverse_iterator it) const { 816 | return reverse_iterator(&*it); 817 | } 818 | const_reverse_iterator unwrap(typename std::vector::const_reverse_iterator it) const { 819 | return const_reverse_iterator(&*it); 820 | } 821 | 822 | void grow_to_external_storage() { 823 | assert(inlined_); 824 | data_internal_.emplace_into(data_external_); 825 | inlined_ = false; 826 | 827 | this->assert_integrity(); 828 | } 829 | 830 | public: 831 | void assert_integrity() const override final { 832 | #ifndef NDEBUG 833 | if (inlined_) { 834 | assert(size_ == data_internal_.size()); 835 | assert(0 == data_external_.size()); 836 | } 837 | else { 838 | assert(0 == data_internal_.size()); 839 | assert(size_ == data_external_.size()); 840 | } 841 | #endif 842 | } 843 | 844 | template 845 | friend std::ostream& operator<<(std::ostream& out, const inlined_vector& vector); 846 | }; 847 | 848 | template 849 | std::ostream& operator<<(std::ostream& out, const inlined_vector& vector) { 850 | out << "inlined_vector "; 851 | out << "(inlined): ["; 852 | if (vector.empty()) 853 | out << "]"; 854 | else { 855 | for (auto it = vector.begin(); it != vector.end(); ++it) { 856 | if (std::next(it) != vector.end()) 857 | out << *it << ", "; 858 | else 859 | out << *it << "]"; 860 | } 861 | } 862 | return out; 863 | } 864 | 865 | template 866 | std::ostream& operator<<(std::ostream& out, const inlined_vector& vector) { 867 | out << "inlined_vector "; 868 | if (vector.inlined_) 869 | out << "(inlined): ["; 870 | else 871 | out << "(external): ["; 872 | if (vector.empty()) 873 | out << "]"; 874 | else { 875 | for (auto it = vector.begin(); it != vector.end(); ++it) { 876 | if (std::next(it) != vector.end()) 877 | out << *it << ", "; 878 | else 879 | out << *it << "]"; 880 | } 881 | } 882 | return out; 883 | } 884 | } // namespace bsp 885 | 886 | #endif -------------------------------------------------------------------------------- /tests/object_pool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "../include/object_pool.h" 18 | #include "catch.hpp" 19 | #include "container_matcher.h" 20 | 21 | using bsp::object_pool; 22 | using bsp::detail::storage_pool; 23 | using bsp::detail::storage_pool_fixed; 24 | using Catch::Equals; 25 | using Catch::StartsWith; 26 | 27 | static bool s_debug_log_allocations = false; 28 | static std::ostringstream s_debug_log_stream; 29 | 30 | namespace bsp { 31 | template 32 | void log_error(const object_pool&, const char* message){ 33 | std::cerr << "Error: object_pool<" << type_name::get() << ">:" << message << "\n"; 34 | } 35 | 36 | template 37 | void log_allocation(const object_pool&, int count, int bytes){ 38 | if (s_debug_log_allocations){ 39 | if (bytes>0){ 40 | s_debug_log_stream << "Memory: storage_pool<" << type_name::get() << "> allocated " << (bytes / 1024) << "kB (" << count << " objects)"; 41 | } 42 | else { 43 | s_debug_log_stream << "Memory: storage_pool<" << type_name::get() << "> deallocated " << (-bytes / 1024) << "kB (" << -count << " objects)"; 44 | } 45 | } 46 | } 47 | 48 | template 49 | struct type_name> { 50 | static std::string get(){ 51 | std::ostringstream oss; 52 | oss << "vector<" << type_name::get() << ">"; 53 | return oss.str(); 54 | } 55 | }; 56 | 57 | template <> struct type_name { static std::string get(){ return "string"; }}; 58 | template <> struct type_name { static std::string get(){ return "i"; }}; 59 | } 60 | 61 | struct object_pool_shrink_after_clear { 62 | static const bool store_id_in_object = false; 63 | static const bool shrink_after_clear = true; 64 | static bool is_object_iterable(const int&){ return true; } 65 | static void set_object_id(int&, const uint32_t&){} 66 | static uint32_t get_object_id(const int&){return 0;} 67 | }; 68 | 69 | TEST_CASE("object_pool print allocations", "[object_pool]"){ 70 | // Note: the actual size will differ by platform so we just check the stream is printing something 71 | auto clear_debug_stream = [](){ s_debug_log_stream.str({}); }; 72 | 73 | s_debug_log_allocations = true; 74 | clear_debug_stream(); 75 | 76 | SECTION("default construction (int)"){ 77 | { 78 | object_pool pool {512}; 79 | CHECK_THAT(s_debug_log_stream.str(), StartsWith("Memory: storage_pool allocated ")); 80 | clear_debug_stream(); 81 | } 82 | CHECK_THAT(s_debug_log_stream.str(), StartsWith("Memory: storage_pool deallocated ")); 83 | } 84 | 85 | SECTION("default construction (string)"){ 86 | { 87 | object_pool> pool {512}; 88 | CHECK_THAT(s_debug_log_stream.str(), StartsWith("Memory: storage_pool> allocated ")); 89 | clear_debug_stream(); 90 | } 91 | CHECK_THAT(s_debug_log_stream.str(), StartsWith("Memory: storage_pool> deallocated ")); 92 | } 93 | 94 | SECTION("expanding storage and shrink after clear"){ 95 | { 96 | object_pool pool{ 512 }; 97 | CHECK(pool.objects().storage_count() == 1); 98 | CHECK_THAT(s_debug_log_stream.str(), StartsWith("Memory: storage_pool allocated ")); 99 | clear_debug_stream(); 100 | for (int i=0; i<513; ++i) pool.construct(); 101 | CHECK_THAT(s_debug_log_stream.str(), StartsWith("Memory: storage_pool allocated ")); 102 | CHECK(pool.objects().storage_count() == 2); 103 | clear_debug_stream(); 104 | pool.clear(); 105 | CHECK_THAT(s_debug_log_stream.str(), StartsWith("Memory: storage_pool deallocated ")); 106 | CHECK(pool.objects().storage_count() == 1); 107 | clear_debug_stream(); 108 | } 109 | CHECK_THAT(s_debug_log_stream.str(), StartsWith("Memory: storage_pool deallocated ")); 110 | } 111 | 112 | s_debug_log_allocations = false; 113 | } 114 | 115 | TEST_CASE("storage_pool", "[storage_pool]") { 116 | SECTION("default construction (ints)"){ 117 | storage_pool arr; 118 | CHECK(arr.storage_count() == 0); 119 | } 120 | 121 | SECTION("construction (ints)"){ 122 | storage_pool arr { 512 }; 123 | CHECK(arr.storage_count() == 1); 124 | CHECK(arr.size() == 512); 125 | } 126 | 127 | SECTION("adding storage (ints)"){ 128 | storage_pool arr { 512 }; 129 | CHECK_NOTHROW(arr.allocate(256)); 130 | CHECK(arr.storage_count() == 2); 131 | CHECK(arr.size() == 512 + 256); 132 | } 133 | 134 | SECTION("creating and destroying ints"){ 135 | storage_pool arr { 512 }; 136 | new (&arr[0]) int {42}; 137 | CHECK(arr[0] == 42); 138 | // No need to destroy 139 | } 140 | 141 | using int_vector = std::vector; 142 | 143 | SECTION("default construction (int_vector)"){ 144 | storage_pool arr; 145 | CHECK(arr.storage_count() == 0); 146 | } 147 | 148 | SECTION("construction (int_vector)"){ 149 | storage_pool arr { 512 }; 150 | CHECK(arr.storage_count() == 1); 151 | CHECK(arr.size() == 512); 152 | } 153 | 154 | SECTION("adding storage (int_vector)"){ 155 | storage_pool arr { 512 }; 156 | arr.allocate(256); 157 | CHECK(arr.storage_count() == 2); 158 | CHECK(arr.size() == 512 + 256); 159 | } 160 | 161 | SECTION("creating and destroying (int_vector)"){ 162 | storage_pool arr { 512 }; 163 | new (&arr[0]) int_vector(100, 42); 164 | CHECK(arr[0].size() == 100); 165 | CHECK(arr[0][0] == 42); 166 | (&arr[0])->~int_vector(); 167 | } 168 | 169 | SECTION("construction and destruction (ints)"){ 170 | storage_pool arr; 171 | CHECK(arr.storage_count() == 0); 172 | CHECK(arr.size() == 0); 173 | arr.allocate(512); 174 | CHECK(arr.storage_count() == 1); 175 | CHECK(arr.size() == 512); 176 | arr.allocate(512); 177 | CHECK(arr.storage_count() == 2); 178 | CHECK(arr.size() == 1024); 179 | arr.deallocate(); 180 | CHECK(arr.storage_count() == 1); 181 | CHECK(arr.size() == 512); 182 | arr.deallocate(); 183 | CHECK(arr.storage_count() == 0); 184 | CHECK(arr.size() == 0); 185 | } 186 | } 187 | 188 | TEST_CASE("storage_pool allocation_error", "[.allocation_error]") { 189 | SECTION("allocation error (length_error)"){ 190 | storage_pool pool; 191 | auto max_bytes = std::numeric_limits::size_type>::max(); 192 | auto max_elements = max_bytes / pool.size_of_value(); 193 | int storage_size = 512; 194 | for (int i = 0; i < max_elements / storage_size; ++i){ 195 | pool.allocate(storage_size); 196 | } 197 | CHECK_THROWS_AS(pool.allocate(storage_size), std::length_error); 198 | } 199 | 200 | SECTION("allocation error (bad_alloc)"){ 201 | using chunk = uint32_t [256]; // 1 KB 202 | REQUIRE(sizeof(chunk) == 1024); 203 | int storage_size = 1024; // 1 MB per storage 204 | int num_storages = 1024; // 1 GB 205 | 206 | // Set this to an upper limit, eventually it'll throw 207 | static const int max_gigabytes = 128; 208 | static const int max_pools = max_gigabytes; 209 | try { 210 | storage_pool pools[max_pools]; 211 | for (int j = 0; j < max_pools; ++j){ 212 | auto& pool = pools[j]; 213 | for (int i = 0; i < num_storages; ++i){ 214 | pool.allocate(storage_size); 215 | } 216 | } 217 | 218 | CHECK(false); 219 | } 220 | catch (std::bad_alloc&){ 221 | CHECK(true); 222 | } 223 | } 224 | } 225 | 226 | 227 | TEST_CASE("storage_pool_fixed", "[storage_pool_fixed]") { 228 | 229 | SECTION("construction (ints)") { 230 | storage_pool_fixed arr{ 512, 8 }; 231 | CHECK(arr.storage_count() == 1); 232 | CHECK(arr.size() == 512); 233 | } 234 | 235 | SECTION("adding storage (ints)") { 236 | storage_pool_fixed arr{ 512, 8 }; 237 | CHECK_NOTHROW(arr.allocate()); 238 | CHECK(arr.storage_count() == 2); 239 | CHECK(arr.size() == 512 + 512); 240 | } 241 | 242 | SECTION("creating and destroying ints") { 243 | storage_pool_fixed arr{ 512, 8 }; 244 | new (&arr[0]) int{ 42 }; 245 | CHECK(arr[0] == 42); 246 | // No need to destroy 247 | } 248 | 249 | using int_vector = std::vector; 250 | 251 | SECTION("construction (int_vector)") { 252 | storage_pool_fixed arr{ 512, 8 }; 253 | CHECK(arr.storage_count() == 1); 254 | CHECK(arr.size() == 512); 255 | } 256 | 257 | SECTION("adding storage (int_vector)") { 258 | storage_pool_fixed arr{ 512, 8 }; 259 | arr.allocate(); 260 | CHECK(arr.storage_count() == 2); 261 | CHECK(arr.size() == 512 + 512); 262 | } 263 | 264 | SECTION("creating and destroying (int_vector)") { 265 | storage_pool_fixed arr{ 512, 8 }; 266 | new (&arr[0]) int_vector(100, 42); 267 | CHECK(arr[0].size() == 100); 268 | CHECK(arr[0][0] == 42); 269 | (&arr[0])->~int_vector(); 270 | } 271 | 272 | SECTION("construction and destruction (ints)") { 273 | storage_pool_fixed arr { 512, 8 }; 274 | CHECK(arr.storage_count() == 1); 275 | CHECK(arr.size() == 512); 276 | arr.allocate(); 277 | CHECK(arr.storage_count() == 2); 278 | CHECK(arr.size() == 1024); 279 | arr.deallocate(); 280 | CHECK(arr.storage_count() == 1); 281 | CHECK(arr.size() == 512); 282 | arr.deallocate(); 283 | CHECK(arr.storage_count() == 0); 284 | CHECK(arr.size() == 0); 285 | } 286 | } 287 | 288 | TEST_CASE("storage_pool_fixed (benchmarks)", "[!benchmark][storage_pool_fixed]") { 289 | const int page_size = 1024; 290 | const int num_pages = 64; 291 | storage_pool_fixed pool { page_size, num_pages }; 292 | for (int i = 0; i < num_pages - 1; ++i) pool.allocate(); 293 | 294 | BENCHMARK("construct elements") { 295 | for (int i = 0; i < page_size * num_pages; ++i) { 296 | new (&pool[i]) int(i); 297 | } 298 | } 299 | 300 | BENCHMARK("iterate elements") { 301 | for (int i = 0; i < page_size * num_pages; ++i) { 302 | new (&pool[i]) int(i); 303 | } 304 | 305 | for (int i = 0; i < page_size * num_pages; ++i) { 306 | #pragma warning( push ) 307 | #pragma warning( disable : 4189 ) // local variable is initialized but not referenced 308 | volatile int x = pool[i]; 309 | #pragma warning( pop ) 310 | } 311 | } 312 | } 313 | 314 | TEST_CASE("storage_pool (benchmarks)", "[!benchmark][storage_pool]") { 315 | const int page_size = 1024; 316 | const int num_pages = 64; 317 | storage_pool pool{ page_size }; 318 | for (int i = 0; i < num_pages - 1; ++i) pool.allocate(page_size); 319 | 320 | BENCHMARK("construct elements") { 321 | for (int i = 0; i < page_size * num_pages; ++i) { 322 | new (&pool[i]) int(i); 323 | } 324 | } 325 | 326 | BENCHMARK("iterate elements") { 327 | for (int i = 0; i < page_size * num_pages; ++i) { 328 | new (&pool[i]) int(i); 329 | } 330 | 331 | for (int i = 0; i < page_size * num_pages; ++i) { 332 | #pragma warning( push ) 333 | #pragma warning( disable : 4189 ) // local variable is initialized but not referenced 334 | volatile int x = pool[i]; 335 | #pragma warning( pop ) 336 | } 337 | } 338 | } 339 | 340 | struct hero { 341 | const char* name = nullptr; 342 | int hp = 0; 343 | int mp = 0; 344 | hero() = default; 345 | hero(const char* name, int hp, int mp):name{name}, hp{hp}, mp{mp}{} 346 | }; 347 | 348 | struct hero_policy { 349 | static const bool store_id_in_object = false; 350 | static const bool shrink_after_clear = false; 351 | static bool is_object_iterable(const hero& value){ return value.hp != 0; } 352 | static void set_object_id(hero&, const uint32_t&){} 353 | static uint32_t get_object_id(const hero&){return 0;} 354 | }; 355 | 356 | struct hero_policy_shrink { 357 | static const bool store_id_in_object = false; 358 | static const bool shrink_after_clear = true; 359 | static bool is_object_iterable(const hero& value){ return value.hp != 0; } 360 | static void set_object_id(hero&, const uint32_t&){} 361 | static uint32_t get_object_id(const hero&){return 0;} 362 | }; 363 | 364 | std::ostream& operator<<(std::ostream& out, const hero& h){ 365 | return out << "hero {name: \"" << h.name << "\", hp: " << h.hp << ", mp: " << h.mp << "}"; 366 | } 367 | 368 | TEST_CASE("object_pool msvc stack overflow??", "[object_pool]") { 369 | SECTION("test 1") { 370 | object_pool heroes{ 64 }; 371 | } 372 | 373 | SECTION("test 2") { 374 | object_pool heroes{ 64 }; 375 | } 376 | } 377 | 378 | TEST_CASE("object_pool (int)", "[object_pool]") { 379 | object_pool pool {512}; 380 | CHECK(pool.size() == 0); 381 | 382 | SECTION("can construct empty"){ 383 | pool.construct(); 384 | CHECK(pool.size() == 1); 385 | } 386 | 387 | SECTION("can construct rvalue"){ 388 | pool.construct(42); 389 | CHECK(pool.size() == 1); 390 | } 391 | 392 | SECTION("id starts at 0 and increments"){ 393 | auto id0 = pool.construct().first; 394 | auto id1 = pool.construct().first; 395 | auto id2 = pool.construct().first; 396 | auto id3 = pool.construct().first; 397 | CHECK(id0 == 0); 398 | CHECK(id1 == 1); 399 | CHECK(id2 == 2); 400 | CHECK(id3 == 3); 401 | } 402 | 403 | SECTION("can remove"){ 404 | for (int i=0; i<10; i++){ 405 | pool.construct((int) std::pow(2, i)).first; 406 | } 407 | CHECK(pool.size() == 10); 408 | std::vector powers { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 }; 409 | CHECK_THAT(pool, Equals(pool, powers)); 410 | 411 | for (int i=0; i<10; i+=2){ 412 | pool.remove(i); 413 | } 414 | CHECK(pool.size() == 5); 415 | std::vector otherPowers { 512, 2, 32, 8, 128 }; 416 | CHECK_THAT(pool, Equals(pool, otherPowers)); 417 | } 418 | 419 | SECTION("front()") { 420 | pool.construct(42); 421 | pool.construct(43); 422 | pool.construct(44); 423 | CHECK(pool.front() == 42); 424 | } 425 | 426 | SECTION("back()") { 427 | pool.construct(42); 428 | pool.construct(43); 429 | pool.construct(44); 430 | CHECK(pool.back() == 44); 431 | } 432 | } 433 | 434 | TEST_CASE("object_pool (std::string)", "[object_pool]") { 435 | object_pool pool {512}; 436 | CHECK(pool.size() == 0); 437 | 438 | SECTION("can construct empty"){ 439 | pool.construct(); 440 | CHECK(pool.size() == 1); 441 | } 442 | 443 | SECTION("can construct rvalue"){ 444 | auto res = pool.construct(std::string("Hello")); 445 | CHECK(pool.size() == 1); 446 | CHECK(res.first == 0); 447 | CHECK(*res.second == "Hello"); 448 | } 449 | 450 | SECTION("can construct move"){ 451 | std::string s {"Hello"}; 452 | auto res = pool.construct(std::move(s)); 453 | CHECK(pool.size() == 1); 454 | CHECK(res.first == 0); 455 | CHECK(*res.second == "Hello"); 456 | } 457 | 458 | SECTION("can construct args"){ 459 | pool.construct("Hello"); 460 | CHECK(pool.size() == 1); 461 | } 462 | } 463 | 464 | TEST_CASE("object_pool (hero)", "[object_pool]") { 465 | object_pool pool {512}; 466 | CHECK(pool.size() == 0); 467 | 468 | SECTION("can construct empty"){ 469 | pool.construct(); 470 | CHECK(pool.size() == 1); 471 | } 472 | 473 | SECTION("can construct copy"){ 474 | hero batman {"batman", 5, 3}; 475 | pool.construct(batman); 476 | CHECK(pool.size() == 1); 477 | } 478 | 479 | SECTION("can construct rvalue"){ 480 | pool.construct({"spiderman", 6, 3}); 481 | CHECK(pool.size() == 1); 482 | } 483 | 484 | SECTION("can construct back"){ 485 | pool.construct("flash", 3, 4); 486 | CHECK(pool.size() == 1); 487 | } 488 | } 489 | 490 | TEST_CASE("object_pool object_is_valid (hero)", "[object_pool]") { 491 | object_pool heroes {32}; 492 | CHECK(heroes.size() == 0); 493 | 494 | heroes.construct("batman", 5, 3); 495 | heroes.construct("superman", 0, 2); 496 | heroes.construct("spiderman", 6, 3); 497 | heroes.construct("flash", 3, 4); 498 | 499 | int num_iters = 0; 500 | for (auto it = heroes.begin(); it != heroes.end(); ++it){ 501 | num_iters++; 502 | } 503 | CHECK(num_iters == 3); // Should skip superman because hp = 0 504 | } 505 | 506 | TEST_CASE("object_pool (grow and clear)", "[object_pool]") { 507 | object_pool pool {512}; 508 | CHECK(pool.capacity() == 512); 509 | for (int i=0; i<513; ++i) pool.construct("batman", 5, 5); 510 | CHECK(pool.capacity() == 1024); 511 | pool.clear(); 512 | CHECK(pool.capacity() == 512); 513 | } 514 | 515 | TEST_CASE("object_pool (grow to max size)", "[object_pool]") { 516 | object_pool pool {512}; 517 | for (int i=0; i; 525 | SECTION("all valid"){ 526 | hero_pool heroes {64}; 527 | heroes.construct("batman", 5, 3); 528 | heroes.construct("spiderman", 6, 3); 529 | heroes.construct("flash", 3, 4); 530 | 531 | std::ostringstream oss; 532 | oss << heroes; 533 | auto result = R"(object_pool [hero {name: "batman", hp: 5, mp: 3}, hero {name: "spiderman", hp: 6, mp: 3}, hero {name: "flash", hp: 3, mp: 4}])"; 534 | CHECK_THAT(oss.str(), Equals(result)); 535 | } 536 | 537 | SECTION("start invalid"){ 538 | hero_pool heroes {64}; 539 | heroes.construct("superman", 0, 3); 540 | heroes.construct("batman", 5, 3); 541 | heroes.construct("spiderman", 6, 3); 542 | heroes.construct("flash", 3, 4); 543 | 544 | std::ostringstream oss; 545 | oss << heroes; 546 | auto result = R"(object_pool [hero {name: "batman", hp: 5, mp: 3}, hero {name: "spiderman", hp: 6, mp: 3}, hero {name: "flash", hp: 3, mp: 4}])"; 547 | CHECK_THAT(oss.str(), Equals(result)); 548 | } 549 | 550 | SECTION("middle invalid"){ 551 | hero_pool heroes {64}; 552 | heroes.construct("batman", 5, 3); 553 | heroes.construct("superman", 0, 3); 554 | heroes.construct("spiderman", 6, 3); 555 | heroes.construct("flash", 3, 4); 556 | 557 | std::ostringstream oss; 558 | oss << heroes; 559 | auto result = R"(object_pool [hero {name: "batman", hp: 5, mp: 3}, hero {name: "spiderman", hp: 6, mp: 3}, hero {name: "flash", hp: 3, mp: 4}])"; 560 | CHECK_THAT(oss.str(), Equals(result)); 561 | } 562 | 563 | SECTION("end invalid"){ 564 | hero_pool heroes {64}; 565 | heroes.construct("batman", 5, 3); 566 | heroes.construct("spiderman", 6, 3); 567 | heroes.construct("flash", 3, 4); 568 | heroes.construct("superman", 0, 3); 569 | 570 | std::ostringstream oss; 571 | oss << heroes; 572 | auto result = R"(object_pool [hero {name: "batman", hp: 5, mp: 3}, hero {name: "spiderman", hp: 6, mp: 3}, hero {name: "flash", hp: 3, mp: 4}])"; 573 | CHECK_THAT(oss.str(), Equals(result)); 574 | } 575 | 576 | SECTION("all invalid"){ 577 | hero_pool heroes {64}; 578 | heroes.construct("batman", 0, 3); 579 | heroes.construct("spiderman", 0, 3); 580 | heroes.construct("flash", 0, 4); 581 | heroes.construct("superman", 0, 3); 582 | 583 | std::ostringstream oss; 584 | oss << heroes; 585 | auto result = R"(object_pool [])"; 586 | CHECK_THAT(oss.str(), Equals(result)); 587 | } 588 | } 589 | struct custom_id { 590 | uint32_t id = 0; 591 | explicit custom_id(uint32_t id = 0): id(id){} 592 | explicit operator uint32_t() const { return id; } 593 | }; 594 | 595 | TEST_CASE("object_pool (hero, custom id)", "[object_pool]") { 596 | object_pool pool {512}; 597 | auto id1 = pool.construct("batman", 5, 3); 598 | CHECK((uint32_t) id1.first == 0); 599 | auto id2 = pool.construct("superman", 999, 4); 600 | CHECK((uint32_t) id2.first == 1); 601 | } 602 | 603 | struct quote { 604 | uint32_t id = 0; 605 | std::string text {}; 606 | quote() = default; 607 | quote(std::string text):text{text}{} 608 | }; 609 | 610 | struct quote_policy { 611 | static const bool store_id_in_object = true; 612 | static const bool shrink_after_clear = true; 613 | static bool is_object_iterable(const quote& value){ return value.id != 0; } 614 | static void set_object_id(quote& value, const uint32_t& id){ value.id = id; } 615 | static uint32_t get_object_id(const quote& value) { return value.id; } 616 | }; 617 | 618 | TEST_CASE("object_pool (object with id)", "[object_pool]") { 619 | object_pool pool {512}; 620 | // This system has id==0 as invalid, so we make an invalid quote first 621 | pool.construct(); 622 | CHECK(std::distance(pool.begin(), pool.end()) == 0); 623 | 624 | auto id1 = pool.construct("The unexamined life is not worth living."); 625 | auto id2 = pool.construct("The only true wisdom is in knowing you know nothing."); 626 | auto id3 = pool.construct("There is only one good, knowledge, and one evil, ignorance."); 627 | 628 | const auto& quote1 = pool[id1.first]; 629 | const auto& quote2 = pool[id2.first]; 630 | const auto& quote3 = pool[id3.first]; 631 | 632 | CHECK(quote1.id == id1.first); 633 | CHECK(quote2.id == id2.first); 634 | CHECK(quote3.id == id3.first); 635 | } 636 | 637 | TEST_CASE("object_pool (internal consistency)", "[object_pool]") { 638 | object_pool pool {8}; 639 | pool.debug_check_internal_consistency(); 640 | 641 | std::default_random_engine engine {0}; 642 | 643 | auto random_int = [&engine](int from, int to){ 644 | return std::uniform_int_distribution{from, to}(engine); 645 | }; 646 | 647 | SECTION("fill below capacity") { 648 | for (int i = 0; i < 4; ++i) { 649 | pool.construct(random_int(0, 100)); 650 | pool.debug_check_internal_consistency(); 651 | } 652 | } 653 | 654 | SECTION("fill to capacity") { 655 | for (int i = 0; i < 8; ++i) { 656 | pool.construct(random_int(0, 100)); 657 | pool.debug_check_internal_consistency(); 658 | } 659 | } 660 | 661 | 662 | SECTION("don't grow") { 663 | std::list ids; 664 | for (int i = 0; i < 4; ++i) { 665 | auto res = pool.construct(random_int(0, 100)); 666 | ids.push_back(res.first); 667 | } 668 | 669 | pool.debug_check_internal_consistency(); 670 | 671 | // Randomly insert and remove things and then check consistency of freelist 672 | for (int i = 0; i < 2; ++i) { 673 | if (random_int(0, 1) == 0) { 674 | auto res = pool.construct(random_int(0, 100)); 675 | ids.push_back(res.first); 676 | } 677 | else { 678 | auto it = std::next(ids.begin(), random_int(0, (int) ids.size() - 1)); 679 | pool.remove(*it); 680 | ids.erase(it); 681 | } 682 | 683 | 684 | pool.debug_check_internal_consistency(); 685 | } 686 | } 687 | 688 | SECTION("grow") { 689 | std::list ids; 690 | for (int i = 0; i < 4; ++i) { 691 | auto res = pool.construct(random_int(0, 100)); 692 | ids.push_back(res.first); 693 | } 694 | pool.debug_check_internal_consistency(); 695 | 696 | for (auto id : ids) { 697 | assert(pool.count(id) > 0); 698 | } 699 | 700 | for (int i = 0; i < 8; ++i) { 701 | if (random_int(0, 4) > 0){ 702 | auto res = pool.construct(random_int(0, 100)); 703 | ids.push_back(res.first); 704 | for (auto id : ids) { 705 | assert(pool.count(id) > 0); 706 | } 707 | pool.debug_check_internal_consistency(); 708 | } 709 | else { 710 | auto it = std::next(ids.begin(), random_int(0, (int) ids.size() - 1)); 711 | pool.remove(*it); 712 | ids.erase(it); 713 | for (auto id : ids) { 714 | assert(pool.count(id) > 0); 715 | } 716 | pool.debug_check_internal_consistency(); 717 | } 718 | } 719 | } 720 | 721 | /* 722 | SECTION("fill to max_size()") { 723 | for (int i = 0; i < pool.max_size(); ++i) { 724 | pool.construct(random_int(0, 100)); 725 | if (i % 64 == 0) pool.debug_check_internal_consistency(); 726 | } 727 | } 728 | */ 729 | } 730 | 731 | #ifdef DEBUG_MEMORY 732 | 733 | struct CrazyObject { 734 | CrazyObject(int i) :data{ new int {i} } {} 735 | ~CrazyObject() { delete data; } 736 | CrazyObject(const CrazyObject& rhs) { 737 | 738 | } 739 | CrazyObject(CrazyObject&& rhs){ 740 | data = new int { *rhs.data }; 741 | // NB: don't delete old data 742 | } 743 | 744 | int* data = nullptr; 745 | }; 746 | 747 | TEST_CASE("object_pool (check for mem leak from move_into)") { 748 | object_pool pool{ 512 }; 749 | auto id1 = pool.construct(1); 750 | auto id2 = pool.construct(2); 751 | auto id3 = pool.construct(3); 752 | pool.remove(id1.first); 753 | CHECK(true); 754 | } 755 | 756 | #endif 757 | 758 | TEST_CASE("object_pool (benchmarks)", "[!benchmark]"){ 759 | static const int page_size = 512; 760 | static const int num_pages = 32; 761 | 762 | BENCHMARK("construct elements") { 763 | object_pool pool{ page_size }; 764 | for(int i = 0; i < page_size * num_pages; ++i) 765 | pool.construct(i); 766 | } 767 | 768 | BENCHMARK("iterate elements") { 769 | object_pool pool{ page_size }; 770 | for (int i = 0; i < page_size * num_pages; ++i) 771 | pool.construct(i); 772 | 773 | for (int i = 0; i < page_size * num_pages; ++i) { 774 | #pragma warning( push ) 775 | #pragma warning( disable : 4189 ) // local variable is initialized but not referenced 776 | volatile int x = pool[i]; 777 | #pragma warning( pop ) 778 | } 779 | } 780 | } 781 | 782 | struct simple_id { 783 | uint32_t id = 0; 784 | uint32_t data = 0; 785 | }; 786 | 787 | struct simple_id_policy { 788 | static const bool store_id_in_object = true; 789 | static const bool shrink_after_clear = false; 790 | static bool is_object_iterable(const simple_id& value) { 791 | // std::cout << "inspecting " << value.id << "\n"; 792 | return value.id != 0; 793 | } 794 | static void set_object_id(simple_id& value, const uint32_t& id) { value.id = id; } 795 | static uint32_t get_object_id(const simple_id& value) { return value.id; } 796 | }; 797 | 798 | TEST_CASE("object_pool (iteration stops early)", "[object_pool]") { 799 | object_pool pool{ 512 }; 800 | 801 | // Blank out memory so all elements have id = 0 802 | auto& objs = pool.objects(); 803 | for (int i = 0; i < objs.storage_count(); ++i) { 804 | auto& storage = objs.storage(i); 805 | std::fill(storage.data, storage.data + storage.count, simple_id{}); 806 | } 807 | 808 | // Create null object 809 | pool.construct(); 810 | 811 | SECTION("Basics") { 812 | auto obj1 = pool.construct(); 813 | obj1.second->data = 42; 814 | auto obj2 = pool.construct(); 815 | obj2.second->data = 13; 816 | auto it = pool.begin(); 817 | while (it != pool.end()) ++it; 818 | CHECK(&(*it) == &(*pool.end())); 819 | } 820 | } 821 | 822 | -------------------------------------------------------------------------------- /include/object_pool.h: -------------------------------------------------------------------------------- 1 | // To customise the behaviour of the object pool 2 | // supply an object pool policy as a template parameter. 3 | // See bsp::detail::default_object_pool_policy for the format. 4 | 5 | #ifndef BSP_OBJECT_POOL_H 6 | #define BSP_OBJECT_POOL_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #define HAS_BAD_ARRAY_NEW_LENGTH 27 | 28 | // #if defined(__GNUC__) // && !defined(__clang__) 29 | #if defined(__GNUC__) 30 | #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) 31 | #if GCC_VERSION < 40902 32 | #undef HAS_BAD_ARRAY_NEW_LENGTH 33 | #endif 34 | #endif 35 | 36 | namespace bsp { 37 | 38 | template void log_allocation(const T& owner, int count, int bytes){} 39 | 40 | template void log_error(const T& owner, const char* message){} 41 | 42 | template struct type_name { 43 | static std::string get(){ 44 | return std::string(typeid(T).name()); 45 | } 46 | }; 47 | 48 | namespace detail { 49 | 50 | // Manages a list of uninitialised storages for T 51 | // Typically use is to access storage() directly 52 | // Note: Direct access through operator[] is O(N = storage.storage_count()) 53 | // Note: Maximum bytes is 2'147'483'647B 54 | template class storage_pool { 55 | public: 56 | using value_type = T; 57 | using reference = T&; 58 | using const_reference = const T&; 59 | using size_type = int; 60 | using aligned_storage_type = typename std::aligned_storage::type; 61 | 62 | struct storage_type { 63 | size_type bytes = 0; 64 | size_type count = 0; 65 | size_type offset = 0; 66 | T* data = nullptr; 67 | 68 | storage_type() = default; 69 | storage_type(size_type bytes, size_type count, size_type offset, T* data):bytes(bytes), count(count), offset(offset), data(data){} 70 | storage_type(const storage_type&) = delete; 71 | storage_type& operator=(const storage_type&) = delete; 72 | storage_type(storage_type&& rhs):bytes(rhs.bytes), count(rhs.count), offset(rhs.offset), data(rhs.data){ 73 | rhs.bytes = 0; 74 | rhs.count = 0; 75 | rhs.offset = 0; 76 | rhs.data = nullptr; 77 | } 78 | }; 79 | 80 | public: 81 | storage_pool() = default; 82 | 83 | explicit storage_pool(size_type count){ 84 | assert(count > 0); 85 | allocate(count); 86 | } 87 | 88 | storage_pool(const storage_pool&) = delete; 89 | storage_pool& operator=(const storage_pool&) = delete; 90 | storage_pool(storage_pool&&) = delete; 91 | storage_pool& operator=(storage_pool&&) = delete; 92 | 93 | ~storage_pool(){ 94 | for (auto& s: storages_) destroy(s); 95 | } 96 | 97 | std::pair attempt_allocation(size_type max_new_objects, std::function errorCallback, std::function allocationErrorCallback) { 98 | size_type num_new_objects = max_new_objects; 99 | const int resize_attempts = 8; 100 | for (int i = 0; i < resize_attempts; i++) { 101 | try { 102 | allocate(num_new_objects); 103 | return { true, num_new_objects }; 104 | } 105 | #ifdef HAS_BAD_ARRAY_NEW_LENGTH 106 | catch (std::bad_array_new_length& e) { 107 | errorCallback(e.what()); 108 | } 109 | #endif 110 | catch (std::bad_alloc& e) { 111 | errorCallback(e.what()); 112 | } 113 | catch (std::length_error& e) { 114 | errorCallback(e.what()); 115 | } 116 | 117 | allocationErrorCallback(num_new_objects * size_of_value()); 118 | num_new_objects = std::max(1, num_new_objects / 2); 119 | } 120 | return { false, 0 }; 121 | } 122 | 123 | void allocate(size_type size) { 124 | assert(size > 0); 125 | 126 | size_type offset = size_; 127 | size_type new_bytes = size_of_value() * size; 128 | size_type current_bytes = size_of_value() * size_; 129 | static const size_type max_bytes = std::numeric_limits::max(); 130 | if (current_bytes > max_bytes - new_bytes){ 131 | throw std::length_error("object_pool: current_bytes > max_bytes - new_bytes"); 132 | } 133 | T* data = reinterpret_cast(new aligned_storage_type[size]); 134 | assert (data != nullptr); 135 | storages_.emplace_back(new_bytes, size, offset, data); 136 | size_ += size; 137 | } 138 | 139 | // Deallocates the most recently allocated storage 140 | void deallocate(){ 141 | assert (storages_.size() > 0); 142 | auto& back_storage = storages_.back(); 143 | auto count = back_storage.count; 144 | size_ -= count; 145 | destroy(back_storage); 146 | storages_.pop_back(); 147 | } 148 | 149 | inline size_type size() const { return size_; } 150 | 151 | inline size_type bytes() const { return size_ * size_of_value(); } 152 | 153 | inline size_type storage_count() const { return (size_type) storages_.size(); } 154 | 155 | const storage_type& storage(size_type i) const { return *std::next(storages_.begin(), i); } 156 | 157 | inline reference operator[](size_type index) { return const_cast(static_cast(this)->operator[](index)); } 158 | 159 | const_reference operator[](size_type index) const { 160 | for (const auto& d : storages_) { 161 | if (index >= d.offset && index < (d.offset + d.count)) { 162 | return d.data[index - d.offset]; 163 | } 164 | } 165 | assert(false); 166 | return *(storages_.begin()->data); 167 | } 168 | 169 | inline size_type size_of_value() const { return static_cast(sizeof(T)); }; 170 | 171 | protected: 172 | size_type size_ = 0; 173 | std::list storages_; 174 | 175 | protected: 176 | void destroy(storage_type& s){ 177 | if (s.data) delete[]reinterpret_cast(s.data); 178 | s.data = nullptr; 179 | } 180 | }; 181 | 182 | template class storage_pool_fixed { 183 | public: 184 | using value_type = T; 185 | using reference = T & ; 186 | using const_reference = const T&; 187 | using size_type = int; 188 | using aligned_storage_type = typename std::aligned_storage::type; 189 | 190 | struct storage_type { 191 | size_type bytes = 0; 192 | size_type count = 0; 193 | size_type offset = 0; 194 | T* data = nullptr; 195 | 196 | storage_type() = default; 197 | storage_type(size_type bytes, size_type count, size_type offset, T* data) :bytes(bytes), count(count), offset(offset), data(data) {} 198 | storage_type(const storage_type&) = delete; 199 | storage_type& operator=(const storage_type&) = delete; 200 | storage_type(storage_type&& rhs) :bytes(rhs.bytes), count(rhs.count), offset(rhs.offset), data(rhs.data) { 201 | rhs.bytes = 0; 202 | rhs.count = 0; 203 | rhs.offset = 0; 204 | rhs.data = nullptr; 205 | } 206 | }; 207 | 208 | public: 209 | explicit storage_pool_fixed(size_type allocation_size, int max_pages) : allocation_size_(allocation_size), max_pages_(max_pages) { 210 | assert(allocation_size_ > 0); 211 | assert(max_pages_ > 0); 212 | storages_.reserve(max_pages_); 213 | allocate(); 214 | } 215 | 216 | storage_pool_fixed(const storage_pool_fixed&) = delete; 217 | storage_pool_fixed& operator=(const storage_pool_fixed&) = delete; 218 | storage_pool_fixed(storage_pool_fixed&&) = delete; 219 | storage_pool_fixed& operator=(storage_pool_fixed&&) = delete; 220 | 221 | ~storage_pool_fixed() { 222 | for (auto& s : storages_) destroy(s); 223 | } 224 | 225 | std::pair attempt_allocation(size_type max_new_objects, std::function, std::function) { 226 | if (max_new_objects > allocation_size_) { 227 | return { false, 0 }; 228 | } 229 | else { 230 | allocate(); 231 | return { true, allocation_size_ }; 232 | } 233 | } 234 | 235 | void allocate() { 236 | if (static_cast(storages_.size()) == max_pages_) { 237 | throw std::length_error("storage_pool_fixed exceeded page count"); 238 | } 239 | const size_type offset = size_; 240 | const size_type allocation_bytes = size_of_value() * allocation_size_; 241 | T* data = reinterpret_cast(new aligned_storage_type[allocation_size_]); 242 | assert(data != nullptr); 243 | storages_.emplace_back(allocation_bytes, allocation_size_, offset, data); 244 | size_ += allocation_size_; 245 | } 246 | 247 | // Deallocates the most recently allocated storage 248 | void deallocate() { 249 | assert(storages_.size() > 0); 250 | auto& back_storage = storages_.back(); 251 | auto count = back_storage.count; 252 | size_ -= count; 253 | destroy(back_storage); 254 | storages_.pop_back(); 255 | } 256 | 257 | inline size_type size() const { return size_; } 258 | 259 | inline size_type bytes() const { return size_ * size_of_value(); } 260 | 261 | inline size_type storage_count() const { return (size_type)storages_.size(); } 262 | 263 | const storage_type& storage(size_type i) const { return *std::next(storages_.begin(), i); } 264 | 265 | inline reference operator[](size_type index) { return const_cast(static_cast(this)->operator[](index)); } 266 | 267 | const_reference operator[](size_type index) const { 268 | return storages_[index / allocation_size_].data[index % allocation_size_]; 269 | } 270 | 271 | inline size_type size_of_value() const { return static_cast(sizeof(T)); }; 272 | 273 | protected: 274 | size_type size_ = 0; 275 | size_type allocation_size_ = 0; 276 | int max_pages_ = 0; 277 | std::vector storages_; 278 | 279 | protected: 280 | void destroy(storage_type& s) { 281 | if (s.data) delete[]reinterpret_cast(s.data); 282 | s.data = nullptr; 283 | } 284 | }; 285 | 286 | template 287 | class object_pool_iterator: public std::iterator { 288 | public: 289 | using value_type = typename object_pool::value_type; 290 | using reference = typename object_pool::reference; 291 | using const_reference = typename object_pool::const_reference; 292 | using pointer = typename object_pool::pointer; 293 | using const_pointer = typename object_pool::const_pointer; 294 | using size_type = typename object_pool::size_type; 295 | 296 | public: 297 | object_pool_iterator(object_pool& array, size_type index, size_type end_index); 298 | object_pool_iterator& operator++(); 299 | object_pool_iterator operator++(int){ object_pool_iterator tmp(*this); ++(*this); return tmp; } 300 | bool operator==(const object_pool_iterator& rhs) const; 301 | bool operator!=(const object_pool_iterator& rhs) const; 302 | reference operator*(); 303 | pointer operator->(); 304 | const_reference operator*() const; 305 | const_pointer operator->() const; 306 | private: 307 | using storage_pool = typename object_pool::storage_pool; 308 | 309 | object_pool& object_pool_; 310 | storage_pool& storage_pool_; 311 | size_type i_ = 0; 312 | size_type di_ = 0; 313 | size_type end_i_ = 0; 314 | size_type end_di_ = 0; 315 | const typename storage_pool::storage_type* db_ = nullptr; 316 | 317 | template friend class object_pool_const_iterator; 318 | }; 319 | 320 | template 321 | class object_pool_const_iterator: public std::iterator { 322 | public: 323 | using value_type = typename object_pool::value_type; 324 | using const_reference = typename object_pool::const_reference; 325 | using const_pointer = typename object_pool::const_pointer; 326 | using size_type = typename object_pool::size_type; 327 | 328 | public: 329 | object_pool_const_iterator(const object_pool& array, size_type index, size_type end_index); 330 | object_pool_const_iterator(const object_pool_const_iterator&) = default; 331 | object_pool_const_iterator(const object_pool_iterator& it):object_pool_(it.object_pool_), storage_pool_(it.storage_pool_), i_(it.i_), di_(it.di_), end_i_(it.end_i_), end_di_(it.end_di_), db_(it.db_){} 332 | object_pool_const_iterator& operator++(); 333 | object_pool_const_iterator operator++(int){ object_pool_const_iterator tmp(*this); ++(*this); return tmp; } 334 | bool operator==(const object_pool_const_iterator& rhs) const; 335 | bool operator!=(const object_pool_const_iterator& rhs) const; 336 | const_reference operator*() const; 337 | const_pointer operator->() const; 338 | private: 339 | using storage_pool = typename object_pool::storage_pool; 340 | 341 | const object_pool& object_pool_; 342 | const storage_pool& storage_pool_; 343 | size_type i_ = 0; 344 | size_type di_ = 0; 345 | size_type end_i_ = 0; 346 | size_type end_di_ = 0; 347 | const typename storage_pool::storage_type* db_ = nullptr; 348 | }; 349 | } // namespace detail 350 | 351 | namespace detail { 352 | template 353 | struct default_object_pool_policy { 354 | static const bool store_id_in_object = false; 355 | static const bool shrink_after_clear = false; 356 | static bool is_object_iterable(const T&){ return true; } 357 | static void set_object_id(T&, const ID&){} 358 | static ID get_object_id(const T&){return 0;} 359 | }; 360 | } 361 | 362 | class object_pool_base { 363 | public: 364 | virtual ~object_pool_base() = default; 365 | virtual void clear() = 0; 366 | }; 367 | 368 | // A pool that stores objects in contiguous arrays 369 | // Reference: Code is heavily inspired by Bitsquid 370 | template> class object_pool : public object_pool_base { 371 | public: 372 | using id_type = ID; 373 | using value_type = T; 374 | using reference = T&; 375 | using const_reference = const T&; 376 | using pointer = T*; 377 | using const_pointer = const T*; 378 | using size_type = int; 379 | using iterator = detail::object_pool_iterator; 380 | using const_iterator = detail::object_pool_const_iterator; 381 | using object_policy = ObjectPolicy; 382 | // using storage_pool = detail::storage_pool; 383 | using storage_pool = detail::storage_pool_fixed; 384 | 385 | public: 386 | // Construct an object pool (requires size <= max_size()) 387 | explicit object_pool(size_type size) 388 | :initial_capacity_{size}, capacity_{size}, 389 | objects_{size, 1 + max_size() / size} 390 | // objects_{size} 391 | { 392 | if (size > max_size()) throw std::length_error("object_pool: constructor size too large"); 393 | log_allocation_internal(objects_.size(), objects_.bytes()); 394 | clear(); 395 | } 396 | 397 | ~object_pool() final override { 398 | log_deallocation_internal(objects_.size(), objects_.bytes()); 399 | for (size_type i = 0; i < num_objects_; i++) { 400 | destroy(objects_[i]); 401 | } 402 | } 403 | 404 | object_pool(const object_pool&) = delete; 405 | object_pool& operator=(const object_pool&) = delete; 406 | 407 | std::pair construct(const T& value = T()) { 408 | index_type& in = new_index(); 409 | T* nv = new (&objects_[in.index]) T(value); 410 | if (object_policy::store_id_in_object){ 411 | object_policy::set_object_id(*nv, in.id); 412 | } 413 | return { in.id, nv }; 414 | } 415 | 416 | template 417 | std::pair construct(Args&&... args) { 418 | index_type& in = new_index(); 419 | T* nv = new (&objects_[in.index]) T(std::forward(args)...); 420 | if (object_policy::store_id_in_object){ 421 | object_policy::set_object_id(*nv, in.id); 422 | } 423 | return { in.id, nv }; 424 | } 425 | 426 | void remove(id_type id) { 427 | index_type& in = index(id); 428 | assert(in.id == id); 429 | 430 | // increment id to avoid conflicts 431 | const uint32_t id_increment = 0x10000; 432 | in.id = id_type { static_cast(id) + id_increment }; 433 | 434 | T& target = objects_[in.index]; 435 | if (object_policy::store_id_in_object){ 436 | #ifndef NDEBUG 437 | ID target_id = object_policy::get_object_id(target); 438 | assert(target_id == id); 439 | #endif 440 | } 441 | destroy(target); 442 | if (in.index != num_objects_ - 1) { 443 | move_back_into(target, in); 444 | } 445 | num_objects_--; 446 | 447 | // Update enqueue 448 | in.index = std::numeric_limits::max(); 449 | indices_[freelist_enque_].next = mask_index(id); 450 | freelist_enque_ = mask_index(id); 451 | } 452 | 453 | void clear() final override { 454 | for (size_type i = 0; i < num_objects_; i++) { 455 | destroy(objects_[i]); 456 | } 457 | num_objects_ = 0; 458 | for (size_type i = 0; i < max_size_; ++i) { 459 | auto& index = indices_[i]; 460 | index.id = static_cast(i); 461 | index.next = static_cast(i + 1); 462 | index.index = std::numeric_limits::max(); 463 | } 464 | freelist_deque_ = 0; 465 | freelist_enque_ = static_cast(capacity_ - 1); 466 | 467 | if (object_policy::shrink_after_clear){ 468 | while (objects_.storage_count() > 1){ 469 | const auto& storage = objects_.storage(objects_.storage_count() - 1); 470 | auto count = storage.count; 471 | objects_.deallocate(); 472 | log_deallocation_internal(count, count * objects_.size_of_value()); 473 | capacity_ -= count; 474 | } 475 | assert(capacity_ == initial_capacity_); 476 | } 477 | } 478 | 479 | size_type count(id_type id) const { 480 | const index_type& in = index(id); 481 | return (in.id == id && in.index != USHRT_MAX) ? 1 : 0; 482 | } 483 | 484 | reference operator[](id_type id) { 485 | return objects_[index(id).index]; 486 | } 487 | 488 | const_reference operator[](id_type id) const { 489 | return objects_[index(id).index]; 490 | } 491 | 492 | struct index_type; 493 | 494 | size_type count(const index_type& index, id_type id) const { 495 | return (index.id == id && index.index != USHRT_MAX) ? 1 : 0; 496 | } 497 | 498 | reference operator[](const index_type& index) { 499 | return objects_[index.index]; 500 | } 501 | 502 | const_reference operator[](const index_type& index) const { 503 | return objects_[index.index]; 504 | } 505 | 506 | reference front() { return objects_[0]; } 507 | 508 | const_reference front() const { return objects_[0]; } 509 | 510 | reference back() { return objects_[num_objects_-1]; } 511 | 512 | const_reference back() const { return objects_[num_objects_ - 1]; } 513 | 514 | const storage_pool& objects() const { return objects_; } 515 | 516 | bool empty() const { return size() == 0; } 517 | 518 | size_type size() const { return num_objects_; } 519 | 520 | size_type capacity() const { return std::min(capacity_, max_size()); } 521 | 522 | static constexpr size_type max_size() { return max_size_ - 1; } 523 | 524 | iterator begin() { 525 | auto it = iterator(*this, 0, size()); 526 | auto end_ = iterator(*this, size(), size()); 527 | while (!object_policy::is_object_iterable(*it) && it != end_){ 528 | ++it; 529 | } 530 | return it; 531 | } 532 | 533 | iterator end() { return iterator(*this, size(), size()); } 534 | 535 | const_iterator begin() const { 536 | auto it = const_iterator(*this, 0, size()); 537 | auto end_ = const_iterator(*this, size(), size()); 538 | while (!object_policy::is_object_iterable(*it) && it != end_){ 539 | ++it; 540 | } 541 | return it; 542 | } 543 | 544 | const_iterator end() const { return const_iterator(*this, size(), size()); } 545 | 546 | const_iterator cbegin() const { return begin(); } 547 | 548 | const_iterator cend() const { return end(); } 549 | 550 | bool debug_check_internal_consistency() const { 551 | // trace freelist 552 | if (freelist_deque_ == capacity_) { 553 | if (freelist_deque_ != freelist_enque_){ 554 | error("object_pool: freelist_deque_ != freelist_enque_"); 555 | return false; 556 | } 557 | } 558 | else { 559 | int ni = freelist_deque_; 560 | int count = 1; 561 | while (ni != freelist_enque_) { 562 | ni = indices_[ni].next; 563 | count++; 564 | } 565 | if (count != capacity_ - num_objects_){ 566 | error("object_pool: count != capacity_ - num_objects_"); 567 | return false; 568 | } 569 | } 570 | 571 | return true; 572 | } 573 | 574 | protected: 575 | static const size_type max_size_ = 0xffff; 576 | size_type initial_capacity_ = 0; 577 | size_type capacity_ = 0; 578 | size_type num_objects_ = 0; 579 | uint16_t freelist_enque_ = 0; 580 | uint16_t freelist_deque_ = 0; 581 | 582 | public: 583 | struct index_type { 584 | id_type id = static_cast(0); 585 | uint16_t index = 0; 586 | uint16_t next = 0; 587 | }; 588 | 589 | index_type& index(id_type id) { 590 | return indices_[mask_index(id)]; 591 | } 592 | 593 | const index_type& index(id_type id) const { 594 | return indices_[mask_index(id)]; 595 | } 596 | 597 | protected: 598 | std::array indices_; 599 | storage_pool objects_; 600 | 601 | protected: 602 | uint16_t mask_index(id_type id) const { 603 | const uint32_t index_mask = 0xffff; 604 | return static_cast(static_cast(id) & index_mask); 605 | } 606 | 607 | void allocate() { 608 | size_type new_size = std::min(capacity_ + initial_capacity_, max_size() + 1); 609 | size_type max_new_objects = new_size - capacity_; 610 | auto result = objects_.attempt_allocation(max_new_objects, [&](const char* str) { error(str); }, [&](size_type bytes) { allocation_error(bytes); }); 611 | if (!result.first) { 612 | throw std::length_error("object_pool: cannot append more storage"); 613 | } 614 | size_type num_new_objects = result.second; 615 | log_allocation_internal(num_new_objects, num_new_objects * objects_.size_of_value()); 616 | } 617 | 618 | index_type& new_index() { 619 | if (num_objects_ >= max_size()) { 620 | throw std::length_error("object_pool: maximum capacity exceeded"); 621 | } 622 | 623 | if (num_objects_ >= capacity_ - 1) { 624 | allocate(); 625 | capacity_ = objects_.size(); 626 | indices_[freelist_enque_].next = static_cast(num_objects_ + 1); 627 | freelist_enque_ = static_cast(capacity_ - 1); 628 | } 629 | 630 | index_type& in = indices_[freelist_deque_]; 631 | freelist_deque_ = in.next; 632 | in.index = static_cast(num_objects_); 633 | num_objects_++; 634 | return in; 635 | } 636 | 637 | void destroy(T& object){ 638 | object.~T(); 639 | } 640 | 641 | void move_back_into(T& target, index_type& index_){ 642 | new (&target) T(std::move(objects_[num_objects_ - 1])); 643 | destroy(objects_[num_objects_ - 1]); 644 | if (object_policy::store_id_in_object){ 645 | index(object_policy::get_object_id(target)).index = index_.index; 646 | } 647 | else { 648 | for (size_type i = 0; i < max_size_; ++i){ 649 | if (indices_[i].index == num_objects_ - 1){ 650 | indices_[i].index = index_.index; 651 | break; 652 | } 653 | } 654 | } 655 | } 656 | 657 | void allocation_error(size_type bytes) const { 658 | std::ostringstream oss; 659 | oss << "couldn't allocate new memory (attempted " << (bytes / 1024) << "kB)"; 660 | log_error(*this, oss.str().c_str()); 661 | } 662 | 663 | void error(const char* message) const { 664 | log_error(*this, message); 665 | } 666 | 667 | void log_allocation_internal(size_type count, size_type bytes) const { 668 | log_allocation(*this, count, bytes); 669 | } 670 | 671 | void log_deallocation_internal(size_type count, size_type bytes) const { 672 | log_allocation(*this, count, -bytes); 673 | } 674 | 675 | template friend class detail::object_pool_iterator; 676 | template friend class detail::object_pool_const_iterator; 677 | 678 | template 679 | friend std::ostream& operator<<(std::ostream&, const object_pool&); 680 | }; 681 | 682 | template const typename object_pool::size_type object_pool::max_size_; 683 | 684 | template 685 | std::ostream& operator<<(std::ostream& out, const object_pool& pool){ 686 | out << "object_pool ["; 687 | auto it = pool.begin(); 688 | auto end = pool.end(); 689 | if (it == end){ 690 | out << "]"; 691 | } 692 | else { 693 | for (; it != end; ++it) { 694 | if (std::next(it) != end) 695 | out << *it << ", "; 696 | else 697 | out << *it << "]"; 698 | } 699 | } 700 | return out; 701 | } 702 | 703 | } 704 | 705 | // Iterator implementations 706 | 707 | namespace bsp { 708 | namespace detail { 709 | 710 | template 711 | object_pool_iterator::object_pool_iterator(object_pool& array, typename object_pool::size_type ri, typename object_pool::size_type end_ri) : object_pool_(array), storage_pool_(array.objects_), i_(0), di_(0), end_i_(0), end_di_(0) { 712 | for (; di_ < storage_pool_.storage_count(); di_++) { 713 | auto& dbz = storage_pool_.storage(di_); 714 | if (ri >= dbz.offset && ri < (dbz.offset + dbz.count)) { 715 | i_ = ri - dbz.offset; 716 | db_ = &dbz; 717 | break; 718 | } 719 | } 720 | 721 | for (; end_di_ < storage_pool_.storage_count(); end_di_++) { 722 | auto& dbz = storage_pool_.storage(end_di_); 723 | if (end_ri >= dbz.offset && end_ri < (dbz.offset + dbz.count)) { 724 | end_i_ = end_ri - dbz.offset; 725 | break; 726 | } 727 | } 728 | } 729 | 730 | template 731 | object_pool_iterator& object_pool_iterator::operator++() { 732 | while (true) { 733 | ++i_; 734 | if (i_ == db_->count) { 735 | // Go to next datablock 736 | i_ = 0; 737 | ++di_; 738 | if (di_ >= storage_pool_.storage_count()) return *this; 739 | else db_ = &storage_pool_.storage(di_); 740 | } 741 | if ((di_ == end_di_ && i_ >= end_i_) || (di_ > end_di_)) return *this; 742 | const auto& value = db_->data[i_]; 743 | if (object_pool::object_policy::is_object_iterable(value)) break; 744 | } 745 | return *this; 746 | } 747 | 748 | template 749 | bool object_pool_iterator::operator==(const object_pool_iterator& rhs) const { 750 | return (i_ == rhs.i_ && di_ == rhs.di_) || (di_ == rhs.di_ && i_ >= rhs.i_) || (di_ > rhs.di_); 751 | } 752 | 753 | template 754 | bool object_pool_iterator::operator!=(const object_pool_iterator& rhs) const { 755 | return !(*this == rhs); 756 | } 757 | 758 | template typename object_pool_iterator::reference object_pool_iterator::operator*() { return db_->data[i_]; } 759 | template typename object_pool_iterator::const_reference object_pool_iterator::operator*() const { return db_->data[i_]; } 760 | 761 | template typename object_pool_iterator::pointer object_pool_iterator::operator->() { return &db_->data[i_]; } 762 | template typename object_pool_iterator::const_pointer object_pool_iterator::operator->() const { return &db_->data[i_]; } 763 | 764 | template 765 | object_pool_const_iterator::object_pool_const_iterator(const object_pool& array, typename object_pool::size_type ri, typename object_pool::size_type end_ri) : object_pool_(array), storage_pool_(array.objects_), i_(0), di_(0), end_i_(0), end_di_(0) { 766 | for (; di_ < storage_pool_.storage_count(); di_++) { 767 | auto& dbz = storage_pool_.storage(di_); 768 | if (ri >= dbz.offset && ri < (dbz.offset + dbz.count)) { 769 | i_ = ri - dbz.offset; 770 | db_ = &dbz; 771 | break; 772 | } 773 | } 774 | 775 | for (; end_di_ < storage_pool_.storage_count(); end_di_++) { 776 | auto& dbz = storage_pool_.storage(end_di_); 777 | if (end_ri >= dbz.offset && end_ri < (dbz.offset + dbz.count)) { 778 | end_i_ = end_ri - dbz.offset; 779 | break; 780 | } 781 | } 782 | } 783 | 784 | template 785 | object_pool_const_iterator& object_pool_const_iterator::operator++() { 786 | while (true) { 787 | ++i_; 788 | if (i_ == db_->count) { 789 | // Go to next datablock 790 | i_ = 0; 791 | ++di_; 792 | if (di_ >= storage_pool_.storage_count()) return *this; 793 | else db_ = &storage_pool_.storage(di_); 794 | } 795 | if ((di_ == end_di_ && i_ >= end_i_) || (di_ > end_di_)) return *this; 796 | const auto& value = db_->data[i_]; 797 | if (object_pool::object_policy::is_object_iterable(value)) break; 798 | } 799 | return *this; 800 | } 801 | 802 | template 803 | bool object_pool_const_iterator::operator==(const object_pool_const_iterator& rhs) const { 804 | return (i_ == rhs.i_ && di_ == rhs.di_) || (di_ == rhs.di_ && i_ >= rhs.i_) || (di_ > rhs.di_); 805 | } 806 | 807 | template 808 | bool object_pool_const_iterator::operator!=(const object_pool_const_iterator& rhs) const { 809 | return !(*this == rhs); 810 | } 811 | 812 | template typename object_pool_const_iterator::const_reference object_pool_const_iterator::operator*() const { return db_->data[i_]; } 813 | template typename object_pool_const_iterator::const_pointer object_pool_const_iterator::operator->() const { return &db_->data[i_]; } 814 | 815 | } 816 | } // namespace bsp 817 | 818 | #endif 819 | --------------------------------------------------------------------------------