├── .clang-format ├── .github ├── gcc-problem-matcher.json └── workflows │ ├── clang-format.yml │ ├── cppcheck.yml │ ├── linux_cmake.yml │ ├── linux_mkspecs.yml │ ├── macos_cmake.yml │ ├── macos_mkspecs.yml │ ├── nodebug.yml │ ├── raspberry_pi.yml │ ├── valgrind.yml │ ├── windows_cmake.yml │ └── windows_mkspecs.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE.rst ├── NEWS.rst ├── README.rst ├── resolve.json ├── src └── recycle │ ├── no_locking_policy.hpp │ ├── shared_pool.hpp │ └── unique_pool.hpp ├── test ├── recycle_tests.cpp ├── src │ ├── test_no_locking_policy.cpp │ ├── test_resource_pool.cpp │ └── test_unique_pool.cpp └── wscript_build ├── waf └── wscript /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: Align 3 | AlignEscapedNewlinesLeft: 'true' 4 | AlignOperands: 'true' 5 | AlwaysBreakTemplateDeclarations: 'true' 6 | AccessModifierOffset: -4 7 | BreakBeforeBraces: Allman 8 | Standard: Cpp11 9 | IndentWidth: 4 10 | IndentCaseLabels: 'false' 11 | PointerAlignment: Left 12 | TabWidth: 4 13 | UseTab: Never 14 | AllowShortFunctionsOnASingleLine: None 15 | AllowAllParametersOfDeclarationOnNextLine: 'true' 16 | FixNamespaceComments: 'false' 17 | BreakConstructorInitializers: AfterColon 18 | ContinuationIndentWidth: 4 19 | Cpp11BracedListStyle: 'true' 20 | ... 21 | -------------------------------------------------------------------------------- /.github/gcc-problem-matcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "gcc-problem-matcher", 5 | "severity": "error", 6 | "pattern": [ 7 | { 8 | "regexp": "^../../(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", 9 | "file": 1, 10 | "line": 2, 11 | "column": 3, 12 | "severity": 4, 13 | "message": 5 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/clang-format.yml: -------------------------------------------------------------------------------- 1 | name: Clang-Format 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | jobs: 15 | Clang-Format: 16 | uses: steinwurf/clang-format-action/.github/workflows/action.yml@4.0.0 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 19 | cancel-in-progress: true 20 | -------------------------------------------------------------------------------- /.github/workflows/cppcheck.yml: -------------------------------------------------------------------------------- 1 | name: Cppcheck 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | jobs: 14 | docker_check: 15 | name: Cppcheck 16 | uses: steinwurf/cppcheck-action/.github/workflows/action.yml@2.0.0 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 19 | cancel-in-progress: true 20 | -------------------------------------------------------------------------------- /.github/workflows/linux_cmake.yml: -------------------------------------------------------------------------------- 1 | name: Linux Cmake 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | env: 15 | EXTRA_RESOLVE_OPTIONS: ${{ github.event.inputs.extra_resolve_options }} 16 | jobs: 17 | Linux-cmake: 18 | name: Linux Cmake 19 | uses: steinwurf/linux-cmake-action/.github/workflows/action.yml@4.0.0 20 | with: 21 | extra_resolve_options: $EXTRA_RESOLVE_OPTIONS 22 | concurrency: 23 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 24 | cancel-in-progress: true 25 | -------------------------------------------------------------------------------- /.github/workflows/linux_mkspecs.yml: -------------------------------------------------------------------------------- 1 | name: Linux C++ make-specs 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | jobs: 15 | Linux-mkspecs: 16 | uses: steinwurf/linux-mkspecs-action/.github/workflows/action.yml@6.0.0 17 | with: 18 | extra_resolve_options: ${{ github.event.inputs.extra_resolve_options }} 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 21 | cancel-in-progress: true 22 | -------------------------------------------------------------------------------- /.github/workflows/macos_cmake.yml: -------------------------------------------------------------------------------- 1 | name: MacOS Cmake 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | env: 15 | EXTRA_RESOLVE_OPTIONS: ${{ github.event.inputs.extra_resolve_options }} 16 | jobs: 17 | MacOS-cmake: 18 | name: MacOS Cmake 19 | uses: steinwurf/macos-cmake-action/.github/workflows/action.yml@6.0.0 20 | with: 21 | extra_resolve_options: $EXTRA_RESOLVE_OPTIONS 22 | concurrency: 23 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 24 | cancel-in-progress: true 25 | -------------------------------------------------------------------------------- /.github/workflows/macos_mkspecs.yml: -------------------------------------------------------------------------------- 1 | name: MacOS C++ make-specs 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | jobs: 15 | MacOS-mkspecs: 16 | uses: steinwurf/macos-mkspecs-action/.github/workflows/action.yml@7.0.0 17 | with: 18 | extra_resolve_options: ${{ github.event.inputs.extra_resolve_options }} 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 21 | cancel-in-progress: true 22 | -------------------------------------------------------------------------------- /.github/workflows/nodebug.yml: -------------------------------------------------------------------------------- 1 | name: No Assertions 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | jobs: 15 | ndebug: 16 | name: No Assertions 17 | uses: steinwurf/nodebug-action/.github/workflows/action.yml@8.0.0 18 | with: 19 | extra_resolve_options: ${{ github.event.inputs.extra_resolve_options }} 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 22 | cancel-in-progress: true 23 | -------------------------------------------------------------------------------- /.github/workflows/raspberry_pi.yml: -------------------------------------------------------------------------------- 1 | name: Raspberry Pi 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | jobs: 15 | RaspberryPi: 16 | uses: steinwurf/cross-compile-action/.github/workflows/action.yml@6.0.0 17 | with: 18 | cxx_mkspec: cxx_raspberry_gxx83_armv7 19 | extra_resolve_options: ${{ github.event.inputs.extra_resolve_options }} 20 | image: ghcr.io/steinwurf/gcc-8.3.0-raspberrypi-armv7:1.0.0 21 | name: Raspberry Pi 22 | test_binary_directory: build/cxx_raspberry_gxx83_armv7/test 23 | test_binary_name: recycle_tests 24 | test_runner: raspberry_pi 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 27 | cancel-in-progress: true 28 | -------------------------------------------------------------------------------- /.github/workflows/valgrind.yml: -------------------------------------------------------------------------------- 1 | name: Valgrind 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | jobs: 15 | valgrind: 16 | name: Valgrind 17 | uses: steinwurf/valgrind-action/.github/workflows/action.yml@5.0.0 18 | with: 19 | extra_resolve_options: ${{ github.event.inputs.extra_resolve_options }} 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 22 | cancel-in-progress: true 23 | -------------------------------------------------------------------------------- /.github/workflows/windows_cmake.yml: -------------------------------------------------------------------------------- 1 | name: Windows Cmake 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | env: 15 | EXTRA_RESOLVE_OPTIONS: ${{ github.event.inputs.extra_resolve_options }} 16 | jobs: 17 | Windows-cmake: 18 | name: Windows Cmake 19 | uses: steinwurf/windows-cmake-action/.github/workflows/action.yml@4.0.0 20 | with: 21 | extra_resolve_options: $EXTRA_RESOLVE_OPTIONS 22 | concurrency: 23 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 24 | cancel-in-progress: true 25 | -------------------------------------------------------------------------------- /.github/workflows/windows_mkspecs.yml: -------------------------------------------------------------------------------- 1 | name: Windows C++ make-specs 2 | 'on': 3 | workflow_dispatch: 4 | inputs: 5 | extra_resolve_options: 6 | description: Extra Resolve Options 7 | required: false 8 | schedule: 9 | - cron: 0 1 * * * 10 | push: 11 | branches: 12 | - master 13 | pull_request: 14 | jobs: 15 | Windows-mkspecs: 16 | uses: steinwurf/windows-mkspecs-action/.github/workflows/action.yml@6.0.0 17 | with: 18 | extra_resolve_options: ${{ github.event.inputs.extra_resolve_options }} 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 21 | cancel-in-progress: true 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | 9 | # Compiled Static libraries 10 | *.lai 11 | *.la 12 | *.a 13 | 14 | # Python 15 | *.pyc 16 | 17 | # Compiled Doxygen documentation 18 | /doxygen/html 19 | 20 | # Waf files 21 | waf-* 22 | waf3-* 23 | .waf-* 24 | .waf3-* 25 | .lock-* 26 | build 27 | build_current 28 | resolve_symlinks 29 | resolved_dependencies 30 | 31 | # Gnu Global tag files 32 | GPATH 33 | GRTAGS 34 | GSYMS 35 | GTAGS 36 | 37 | # Emacs temp / auto save 38 | \#*# 39 | *.#* 40 | *~ 41 | 42 | #Eclipse ignore 43 | .cproject 44 | *.project 45 | .metadata 46 | local.properties 47 | .classpath 48 | .settings/ 49 | 50 | # Visual Studio ignore 51 | *.bat 52 | *.sln 53 | *.suo 54 | *.user 55 | *.ncb 56 | *.sdf 57 | *.opensdf 58 | *.log 59 | *.vcxproj* 60 | VSProjects 61 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(recycle) 3 | 4 | # Define library 5 | add_library(recycle INTERFACE) 6 | target_compile_features(recycle INTERFACE cxx_std_14) 7 | target_include_directories(recycle INTERFACE src/) 8 | add_library(steinwurf::recycle ALIAS recycle) 9 | 10 | # Install headers 11 | install( 12 | DIRECTORY ./src/recycle 13 | DESTINATION ${CMAKE_INSTALL_PREFIX}/include 14 | FILES_MATCHING 15 | PATTERN *.hpp) 16 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Steinwurf ApS 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | * Neither the name of recycle nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /NEWS.rst: -------------------------------------------------------------------------------- 1 | News for recycle 2 | ================ 3 | 4 | This file lists the major changes between versions. For a more detailed list of 5 | every change, see the Git log. 6 | 7 | Latest 8 | ------ 9 | * tbd 10 | 11 | 7.0.0 12 | ----- 13 | * Major: Use waf-tools 5. 14 | * Minor: Updated waf. 15 | 16 | 6.0.0 17 | ----- 18 | * Major: Change cmake build to be object library based. 19 | 20 | 5.1.0 21 | ----- 22 | * Minor: Added install step to CMake. 23 | 24 | 5.0.0 25 | ----- 26 | * Major: Use std::size_t for size and positions. 27 | 28 | 4.1.2 29 | ----- 30 | * Patch: Fix ``target_compile_features``. 31 | 32 | 4.1.1 33 | ----- 34 | * Patch: Added ``target_compile_features`` to CMake script so that c++14 is 35 | used. 36 | 37 | 4.1.0 38 | ----- 39 | * Minor: Added CMake build file. 40 | 41 | 4.0.0 42 | ----- 43 | * Major: Rename resource_pool to shared_pool 44 | * Minor: Adding unique_pool 45 | 46 | 3.0.0 47 | ----- 48 | * Major: Upgrade to waf-tools 4 49 | * Minor: Upgrade to gtest 4 50 | 51 | 2.0.0 52 | ----- 53 | * Major: Upgrade to waf-tools 3 54 | * Minor: Upgrade to gtest 3 55 | 56 | 1.2.0 57 | ----- 58 | * Patch: Fix a memory leak caused by a circular dependency when using objects 59 | inheriting from ``std::enable_shared_from_this``. 60 | * Minor: Added buildbot.py for coverage reports. 61 | * Patch: Fixed comparison warnings in unit tests. 62 | 63 | 1.1.1 64 | ----- 65 | * Patch: Fix version define. 66 | 67 | 1.1.0 68 | ----- 69 | * Minor: Added version define. 70 | 71 | 1.0.1 72 | ----- 73 | * Patch: Added test for no_locking_policy.hpp 74 | * Patch: Fixed includes 75 | 76 | 1.0.0 77 | ----- 78 | * Major: Initial release of the project. 79 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | recycle 2 | ======= 3 | 4 | |Linux make-specs| |Windows make-specs| |MacOS make-specs| |Linux CMake| |Windows CMake| |MacOS CMake| |Raspberry Pi| |Valgrind| |No Assertions| |Clang Format| |Cppcheck| 5 | 6 | .. |Linux make-specs| image:: https://github.com/steinwurf/recycle/actions/workflows/linux_mkspecs.yml/badge.svg 7 | :target: https://github.com/steinwurf/recycle/actions/workflows/linux_mkspecs.yml 8 | 9 | .. |Windows make-specs| image:: https://github.com/steinwurf/recycle/actions/workflows/windows_mkspecs.yml/badge.svg 10 | :target: https://github.com/steinwurf/recycle/actions/workflows/windows_mkspecs.yml 11 | 12 | .. |MacOS make-specs| image:: https://github.com/steinwurf/recycle/actions/workflows/macos_mkspecs.yml/badge.svg 13 | :target: https://github.com/steinwurf/recycle/actions/workflows/macos_mkspecs.yml 14 | 15 | .. |Linux CMake| image:: https://github.com/steinwurf/recycle/actions/workflows/linux_cmake.yml/badge.svg 16 | :target: https://github.com/steinwurf/recycle/actions/workflows/linux_cmake.yml 17 | 18 | .. |Windows CMake| image:: https://github.com/steinwurf/recycle/actions/workflows/windows_cmake.yml/badge.svg 19 | :target: https://github.com/steinwurf/recycle/actions/workflows/windows_cmake.yml 20 | 21 | .. |MacOS CMake| image:: https://github.com/steinwurf/recycle/actions/workflows/macos_cmake.yml/badge.svg 22 | :target: https://github.com/steinwurf/recycle/actions/workflows/macos_cmake.yml 23 | 24 | .. |Raspberry Pi| image:: https://github.com/steinwurf/recycle/actions/workflows/raspberry_pi.yml/badge.svg 25 | :target: https://github.com/steinwurf/recycle/actions/workflows/raspberry_pi.yml 26 | 27 | .. |Clang Format| image:: https://github.com/steinwurf/recycle/actions/workflows/clang-format.yml/badge.svg 28 | :target: https://github.com/steinwurf/recycle/actions/workflows/clang-format.yml 29 | 30 | .. |No Assertions| image:: https://github.com/steinwurf/recycle/actions/workflows/nodebug.yml/badge.svg 31 | :target: https://github.com/steinwurf/recycle/actions/workflows/nodebug.yml 32 | 33 | .. |Valgrind| image:: https://github.com/steinwurf/recycle/actions/workflows/valgrind.yml/badge.svg 34 | :target: https://github.com/steinwurf/recycle/actions/workflows/valgrind.yml 35 | 36 | .. |Cppcheck| image:: https://github.com/steinwurf/recycle/actions/workflows/cppcheck.yml/badge.svg 37 | :target: https://github.com/steinwurf/recycle/actions/workflows/cppcheck.yml 38 | 39 | recycle is an implementation of a simple C++ resource pool. 40 | 41 | .. contents:: Table of Contents: 42 | :local: 43 | 44 | Usage 45 | ----- 46 | 47 | The ``recycle`` project contains two types of resource pools: 48 | 49 | 1. The ``recycle::shared_pool`` is useful when managing expensive to 50 | construct objects. The life-time of the managed objects is controlled 51 | by using ``std::shared_ptr``. A custom deleter is used to reclaim 52 | objects in the pool when the last remaining ``std::shared_ptr`` owning 53 | the object is destroyed. 54 | 55 | 2. The ``recycle::unique_pool`` works the same way as the 56 | ``recycle::shared_pool`` but instead uses ``std::unique_ptr`` for 57 | managing the resources. Still we need a custom deleter - with 58 | ``std::unique_ptr`` this has to be part of the type. So the 59 | ``std::unique_ptr`` returned by ``recycle::unique_pool`` is 60 | of type ``recycle::unique_pool::pool_ptr``. 61 | 62 | Besides the fact that ``recycle::shared_pool`` manages ``std::shared_ptr`` and 63 | ``recycle::unique_pool`` manages ``std::unique_ptr`` the API should be the 64 | same. So in the following you can replace ``shared`` with ``unique`` to 65 | swap the behavior. 66 | 67 | Header-only 68 | ........... 69 | 70 | The library itself is header-only so essentially to use it you just 71 | have to clone the repository and setup the right include paths in the 72 | project where you would like to use it. 73 | 74 | The library uses C++14 features, so you need a relatively recent compiler 75 | to use it. 76 | 77 | Allocating Objects 78 | ------------------ 79 | 80 | There are two ways we can control how objects are allocated: 81 | 82 | Using the Default Allocator 83 | ........................... 84 | 85 | Example: 86 | 87 | .. code-block:: cpp 88 | 89 | #include 90 | #include 91 | 92 | struct heavy_object 93 | { 94 | // ... some expensive resource 95 | }; 96 | 97 | 98 | recycle::shared_pool pool; 99 | 100 | // Initially the pool is empty 101 | assert(pool.unused_resources() == 0U); 102 | 103 | { 104 | auto o1 = pool.allocate(); 105 | } 106 | 107 | // Heavy object is back in the pool 108 | assert(pool.unused_resources() == 1U); 109 | 110 | In this case we use the default constructor of the 111 | ``recycle::shared_pool`` this will only work if the object in this 112 | case ``heavy_object`` is default constructible (i.e. has a constructor 113 | which takes no arguments). Internally the resource pool uses 114 | ``std::make_shared`` to allocate the object. 115 | 116 | Using a Custom Allocator 117 | ........................ 118 | 119 | Example: 120 | 121 | .. code-block:: cpp 122 | 123 | #include 124 | #include 125 | 126 | struct heavy_object 127 | { 128 | heavy_object(std::size_t size); 129 | 130 | // ... some expensive resource 131 | }; 132 | 133 | auto make = []()->std::shared_ptr 134 | { 135 | return std::make_shared(300000U); 136 | }; 137 | 138 | recycle::shared_pool pool(make); 139 | 140 | auto o1 = pool.allocate(); 141 | 142 | In this case we provide a custom allocator function which takes no 143 | arguments and returns a ``std::shared_ptr``. 144 | 145 | Recycling Objects 146 | ----------------- 147 | 148 | When recycling objects it is sometimes necessary to ensure that 149 | certain clean-up operations are performed before objects get stored in 150 | the pool. This can be open file handles etc. which should be 151 | closed. We cannot rely on the destructor for this when using a resource pool. 152 | 153 | To support this the ``recycle::shared_pool`` support a custom 154 | recycle function which will be called right before an object is about 155 | to go back into the pool. 156 | 157 | Example: 158 | 159 | .. code-block:: cpp 160 | 161 | #include 162 | #include 163 | 164 | struct heavy_object 165 | { 166 | heavy_object(std::size_t size); 167 | 168 | // ... some expensive resource 169 | }; 170 | 171 | auto make = []()->std::shared_ptr 172 | { 173 | return std::make_shared(300000U); 174 | }; 175 | 176 | auto recycle = [](std::shared_ptr o) 177 | { 178 | o->close_sockets(); 179 | }; 180 | 181 | 182 | recycle::shared_pool pool(make, recycle); 183 | 184 | { 185 | auto o1 = pool.allocate(); 186 | 187 | // As we exit the scope here recycle will be called 188 | // with o1 as argument. 189 | } 190 | 191 | Thread Safety 192 | ------------- 193 | 194 | Since the free lunch is over we want to make sure that the resource 195 | pool is thread safe. 196 | 197 | This can be achieved by specifying a lock policy (we were inspired by the 198 | flyweight library in Boost). 199 | 200 | Example: 201 | 202 | .. code-block:: cpp 203 | 204 | #include 205 | #include 206 | #include 207 | 208 | struct heavy_object 209 | { 210 | // ... some expensive resource 211 | }; 212 | 213 | struct lock_policy 214 | { 215 | using mutex_type = std::mutex; 216 | using lock_type = std::lock_guard; 217 | }; 218 | 219 | recycle::shared_pool pool; 220 | 221 | // Lambda the threads will execute captures a reference to the pool 222 | // so they will all operate on the same pool concurrently 223 | auto run = [&pool]() 224 | { 225 | auto a1 = pool.allocate(); 226 | }; 227 | 228 | const std::size_t number_threads = 8; 229 | std::thread t[number_threads]; 230 | 231 | //Launch a group of threads 232 | for (std::size_t i = 0; i < number_threads; ++i) 233 | { 234 | t[i] = std::thread(run); 235 | } 236 | 237 | //Join the threads with the main thread 238 | for (std::size_t i = 0; i < number_threads; ++i) 239 | { 240 | t[i].join(); 241 | } 242 | 243 | Use as Dependency in CMake 244 | -------------------------- 245 | 246 | To depend on this project when using the CMake build system, add the following 247 | in your CMake build script: 248 | 249 | .. code-block:: cmake 250 | 251 | add_subdirectory("/path/to/recycle" recycle) 252 | target_link_libraries( steinwurf::recycle) 253 | 254 | Where ```` is replaced by your target. 255 | -------------------------------------------------------------------------------- /resolve.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "waf-tools", 4 | "resolver": "git", 5 | "method": "semver", 6 | "major": 5, 7 | "sources": [ 8 | "github.com/steinwurf/waf-tools.git" 9 | ] 10 | }, 11 | { 12 | "name": "gtest", 13 | "internal": true, 14 | "resolver": "git", 15 | "method": "semver", 16 | "major": 5, 17 | "sources": [ 18 | "github.com/steinwurf/gtest.git" 19 | ] 20 | } 21 | ] -------------------------------------------------------------------------------- /src/recycle/no_locking_policy.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Steinwurf ApS 2014. 2 | // All Rights Reserved 3 | // 4 | // Distributed under the "BSD License". See the accompanying LICENSE.rst file. 5 | 6 | #pragma once 7 | 8 | namespace recycle 9 | { 10 | /// Defines the default non thread-safe locking policy for the 11 | /// recycle::resource_pool. 12 | /// 13 | /// Custom locking policies may be defined to create a thread-safe 14 | /// resource pool for different threading libraries. 15 | /// 16 | /// A valid locking policy defines two types, namely the mutex and 17 | /// the lock. 18 | /// 19 | /// The following small example illustrates the expected behavior: 20 | /// 21 | /// using mutex = locking_policy::mutex_type; 22 | /// using lock = locking_policy::lock_type; 23 | /// 24 | /// { 25 | /// mutex m; // creates mutex m in unlocked state 26 | /// lock l(m); // associates and locks the mutex m with the lock l. 27 | /// 28 | /// ... // when l's destructor runs it unlocks m 29 | /// } 30 | /// 31 | /// If you wanted to use std::thread then a suitable locking 32 | /// policy could be: 33 | /// 34 | /// struct lock_policy 35 | /// { 36 | /// using mutex_type = std::mutex; 37 | /// using lock_type = std::lock_guard; 38 | /// }; 39 | /// 40 | struct no_locking_policy 41 | { 42 | /// Define dummy mutex type 43 | struct no_mutex 44 | { 45 | }; 46 | 47 | /// Define dummy lock type 48 | struct no_lock 49 | { 50 | no_lock(no_mutex&) 51 | { 52 | } 53 | }; 54 | 55 | /// The locking policy mutex type 56 | using mutex_type = no_mutex; 57 | 58 | /// The locking policy lock type 59 | using lock_type = no_lock; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /src/recycle/shared_pool.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Steinwurf ApS 2014. 2 | // All Rights Reserved 3 | // 4 | // Distributed under the "BSD License". See the accompanying LICENSE.rst file. 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "no_locking_policy.hpp" 18 | 19 | namespace recycle 20 | { 21 | /// @brief The shared pool stores value objects and recycles them. 22 | /// 23 | /// The shared pool is a useful construct if you have some 24 | /// expensive to create objects where you would like to create a 25 | /// factory capable of recycling the objects. 26 | /// 27 | /// 28 | template 29 | class shared_pool 30 | { 31 | public: 32 | /// The type managed 33 | using value_type = Value; 34 | 35 | /// The pointer to the resource 36 | using value_ptr = std::shared_ptr; 37 | 38 | /// The allocate function type 39 | /// Should take no arguments and return an std::shared_ptr to the Value 40 | using allocate_function = std::function; 41 | 42 | /// The recycle function type 43 | /// If specified the recycle function will be called every time a 44 | /// resource gets recycled into the pool. This allows temporary 45 | /// resources, e.g., file handles to be closed when an object is longer 46 | /// used. 47 | using recycle_function = std::function; 48 | 49 | /// The locking policy mutex type 50 | using mutex_type = typename LockingPolicy::mutex_type; 51 | 52 | /// The locking policy lock type 53 | using lock_type = typename LockingPolicy::lock_type; 54 | 55 | public: 56 | /// Default constructor, we only want this to be available 57 | /// i.e. the shared_pool to be default constructible if the 58 | /// value_type we build is default constructible. 59 | /// 60 | /// This means that we only want 61 | /// std::is_default_constructible>::value to 62 | /// be true if the type T is default constructible. 63 | /// 64 | /// Unfortunately this does not work if we don't do the 65 | /// template magic seen below. What we do there is to use 66 | /// SFINAE to disable the default constructor for non default 67 | /// constructible types. 68 | /// 69 | /// It looks quite ugly and if somebody can fix in a simpler way 70 | /// please do :) 71 | template ::value, 73 | uint8_t>::type = 0> 74 | shared_pool() : 75 | m_pool(std::make_shared( 76 | allocate_function(std::make_shared))) 77 | { 78 | } 79 | 80 | /// Create a shared pool using a specific allocate function. 81 | /// @param allocate Allocation function 82 | shared_pool(allocate_function allocate) : 83 | m_pool(std::make_shared(std::move(allocate))) 84 | { 85 | } 86 | 87 | /// Create a shared pool using a specific allocate function and 88 | /// recycle function. 89 | /// @param allocate Allocation function 90 | /// @param recycle Recycle function 91 | shared_pool(allocate_function allocate, recycle_function recycle) : 92 | m_pool(std::make_shared(std::move(allocate), std::move(recycle))) 93 | { 94 | } 95 | 96 | /// Copy constructor 97 | shared_pool(const shared_pool& other) : 98 | m_pool(std::make_shared(*other.m_pool)) 99 | { 100 | } 101 | 102 | /// Move constructor 103 | shared_pool(shared_pool&& other) : m_pool(std::move(other.m_pool)) 104 | { 105 | assert(m_pool); 106 | } 107 | 108 | /// Copy assignment 109 | shared_pool& operator=(const shared_pool& other) 110 | { 111 | shared_pool tmp(other); 112 | std::swap(*this, tmp); 113 | return *this; 114 | } 115 | 116 | /// Move assignment 117 | shared_pool& operator=(shared_pool&& other) 118 | { 119 | m_pool = std::move(other.m_pool); 120 | return *this; 121 | } 122 | 123 | /// @returns the number of unused resources 124 | std::size_t unused_resources() const 125 | { 126 | assert(m_pool); 127 | return m_pool->unused_resources(); 128 | } 129 | 130 | /// Frees all unused resources 131 | void free_unused() 132 | { 133 | assert(m_pool); 134 | m_pool->free_unused(); 135 | } 136 | 137 | /// @return A resource from the pool. 138 | value_ptr allocate() 139 | { 140 | assert(m_pool); 141 | return m_pool->allocate(); 142 | } 143 | 144 | private: 145 | /// The actual pool implementation. We use the 146 | /// enable_shared_from_this helper to make sure we can pass a 147 | /// "back-pointer" to the pooled objects. The idea behind this 148 | /// is that we need objects to be able to add themselves back 149 | /// into the pool once they go out of scope. 150 | struct impl : public std::enable_shared_from_this 151 | { 152 | /// @copydoc shared_pool::shared_pool(allocate_function) 153 | impl(allocate_function allocate) : m_allocate(std::move(allocate)) 154 | { 155 | assert(m_allocate); 156 | } 157 | 158 | /// @copydoc shared_pool::shared_pool(allocate_function, 159 | /// recycle_function) 160 | impl(allocate_function allocate, recycle_function recycle) : 161 | m_allocate(std::move(allocate)), m_recycle(std::move(recycle)) 162 | { 163 | assert(m_allocate); 164 | assert(m_recycle); 165 | } 166 | 167 | /// Copy constructor 168 | impl(const impl& other) : 169 | std::enable_shared_from_this(other), 170 | m_allocate(other.m_allocate), m_recycle(other.m_recycle) 171 | { 172 | std::size_t size = other.unused_resources(); 173 | for (std::size_t i = 0; i < size; ++i) 174 | { 175 | m_free_list.push_back(m_allocate()); 176 | } 177 | } 178 | 179 | /// Move constructor 180 | impl(impl&& other) : 181 | std::enable_shared_from_this(other), 182 | m_allocate(std::move(other.m_allocate)), 183 | m_recycle(std::move(other.m_recycle)), 184 | m_free_list(std::move(other.m_free_list)) 185 | { 186 | } 187 | 188 | /// Copy assignment 189 | impl& operator=(const impl& other) 190 | { 191 | impl tmp(other); 192 | std::swap(*this, tmp); 193 | return *this; 194 | } 195 | 196 | /// Move assignment 197 | impl& operator=(impl&& other) 198 | { 199 | m_allocate = std::move(other.m_allocate); 200 | m_recycle = std::move(other.m_recycle); 201 | m_free_list = std::move(other.m_free_list); 202 | return *this; 203 | } 204 | 205 | /// Allocate a new value from the pool 206 | value_ptr allocate() 207 | { 208 | value_ptr resource; 209 | 210 | { 211 | lock_type lock(m_mutex); 212 | 213 | if (m_free_list.size() > 0) 214 | { 215 | resource = m_free_list.back(); 216 | m_free_list.pop_back(); 217 | } 218 | } 219 | 220 | if (!resource) 221 | { 222 | assert(m_allocate); 223 | resource = m_allocate(); 224 | } 225 | 226 | auto pool = impl::shared_from_this(); 227 | 228 | // Here we create a std::shared_ptr with a naked 229 | // pointer to the resource and a custom deleter 230 | // object. The custom deleter object stores two 231 | // things: 232 | // 233 | // 1. A std::weak_ptr to the pool (used when we 234 | // need to put the resource back in the pool). If 235 | // the pool dies before the resource then we can 236 | // detect this with the weak_ptr and no try to 237 | // access it. 238 | // 239 | // 2. A std::shared_ptr that points to the actual 240 | // resource and is the one actually keeping it alive. 241 | 242 | return value_ptr(resource.get(), deleter(pool, resource)); 243 | } 244 | 245 | /// @copydoc shared_pool::free_unused() 246 | void free_unused() 247 | { 248 | lock_type lock(m_mutex); 249 | m_free_list.clear(); 250 | } 251 | 252 | /// @copydoc shared_pool::unused_resources() 253 | std::size_t unused_resources() const 254 | { 255 | lock_type lock(m_mutex); 256 | return m_free_list.size(); 257 | } 258 | 259 | /// This function called when a resource should be added 260 | /// back into the pool 261 | void recycle(const value_ptr& resource) 262 | { 263 | if (m_recycle) 264 | { 265 | m_recycle(resource); 266 | } 267 | 268 | lock_type lock(m_mutex); 269 | m_free_list.push_back(resource); 270 | } 271 | 272 | private: 273 | /// The allocator to use 274 | allocate_function m_allocate; 275 | 276 | /// The recycle function 277 | recycle_function m_recycle; 278 | 279 | /// Stores all the free resources 280 | std::list m_free_list; 281 | 282 | /// Mutex used to coordinate access to the pool. We had to 283 | /// make it mutable as we have to lock in the 284 | /// unused_resources() function. Otherwise we can have a 285 | /// race condition on the size it returns. I.e. if one 286 | /// threads releases a resource into the free list while 287 | /// another tries to read its size. 288 | mutable mutex_type m_mutex; 289 | }; 290 | 291 | /// The custom deleter object used by the std::shared_ptr 292 | /// to de-allocate the object if the pool goes out of 293 | /// scope. When a std::shared_ptr wants to de-allocate the 294 | /// object contained it will call the operator() define here. 295 | struct deleter 296 | { 297 | /// @param pool. A weak_ptr to the pool 298 | deleter(const std::weak_ptr& pool, const value_ptr& resource) : 299 | m_pool(pool), m_resource(resource) 300 | { 301 | assert(!m_pool.expired()); 302 | assert(m_resource); 303 | } 304 | 305 | /// Call operator called by std::shared_ptr when 306 | /// de-allocating the object. 307 | void operator()(value_type*) 308 | { 309 | // Place the resource in the free list 310 | auto pool = m_pool.lock(); 311 | 312 | if (pool) 313 | { 314 | pool->recycle(m_resource); 315 | } 316 | 317 | // This reset() is needed because otherwise a circular 318 | // dependency can arise here in special situations. 319 | // 320 | // One example of such a situation is when the value_type 321 | // derives from std::enable_shared_from_this in that case, 322 | // the following will happen: 323 | // 324 | // The std::enable_shared_from_this implementation works by 325 | // storing a std::weak_ptr to itself. This std::weak_ptr 326 | // internally points to an "counted" object keeping track 327 | // of the reference count managing the raw pointer's release 328 | // policy (e.g. storing the custom deleter etc.) for all 329 | // the shared_ptr's. The "counted" object is both kept 330 | // alive by all std::shared_ptr and std::weak_ptr objects. 331 | // 332 | // In this specific case of std::enable_shared_from_this, 333 | // the custom deleter is not destroyed because the internal 334 | // std::weak_ptr still points to the "counted" object and 335 | // inside the custom deleter we are keeping the managed 336 | // object alive because we have a std::shared_ptr to it. 337 | // 338 | // The following diagram show the circular dependency where 339 | // the arrows indicate what is keeping what alive: 340 | // 341 | // +----------------+ +--------------+ 342 | // | custom deleter +--------------+ | real deleter | 343 | // +----------------+ | +--------------+ 344 | // ^ | ^ 345 | // | | | 346 | // | | | 347 | // +-----+--------+ | +-------+------+ 348 | // | shared_count | | | shared_count | 349 | // +--------------+ | +--------------+ 350 | // ^ ^ | ^ 351 | // | | | | 352 | // | | | | 353 | // | | v | 354 | // | | +------------+ +------------+ | 355 | // | +--+ shared_ptr | | shared_ptr +-+ 356 | // | +------------+ +----+-------+ 357 | // | | 358 | // | | 359 | // +----+-----+ +--------+ | 360 | // | weak_ptr |<-----------+ object |<-+ 361 | // +----------+ +--------+ 362 | // 363 | // The std::shared_ptr on the right is the one managed by the 364 | // shared pool, it is the one actually deleting the 365 | // object when it goes out of scope. The shared_ptr on the 366 | // left is the one which contains the custom 367 | // deleter that will return the object into the resource 368 | // pool when it goes out of scope. 369 | // 370 | // By calling reset on the shared_ptr in the custom deleter 371 | // we break the cyclic dependency. 372 | m_resource.reset(); 373 | } 374 | 375 | // Pointer to the pool needed for recycling 376 | std::weak_ptr m_pool; 377 | 378 | // The resource object 379 | value_ptr m_resource; 380 | }; 381 | 382 | private: 383 | // The pool impl 384 | std::shared_ptr m_pool; 385 | }; 386 | } // namespace recycle 387 | -------------------------------------------------------------------------------- /src/recycle/unique_pool.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Steinwurf ApS 2014. 2 | // All Rights Reserved 3 | // 4 | // Distributed under the "BSD License". See the accompanying LICENSE.rst file. 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "no_locking_policy.hpp" 18 | 19 | namespace recycle 20 | { 21 | /// @brief The unique_pool stores value objects and recycles them. 22 | /// 23 | /// The unique_pool is a useful construct if you have some 24 | /// expensive to create objects where you would like to create a 25 | /// factory capable of recycling the objects. 26 | /// 27 | /// 28 | template 29 | class unique_pool 30 | { 31 | private: 32 | /// Forward declare 33 | struct deleter; 34 | struct impl; 35 | 36 | public: 37 | /// The type managed 38 | using value_type = Value; 39 | 40 | /// The pointer to the resource 41 | using pool_ptr = std::unique_ptr; 42 | 43 | /// The owning pointer to the resource 44 | using value_ptr = std::unique_ptr; 45 | 46 | /// The allocate function type 47 | /// Should take no arguments and return an std::unique_ptr to the Value 48 | using allocate_function = std::function; 49 | 50 | /// The recycle function type 51 | /// If specified the recycle function will be called every time a 52 | /// resource gets recycled into the pool. This allows temporary 53 | /// resources, e.g., file handles to be closed when an object is longer 54 | /// used. 55 | using recycle_function = std::function; 56 | 57 | /// The locking policy mutex type 58 | using mutex_type = typename LockingPolicy::mutex_type; 59 | 60 | /// The locking policy lock type 61 | using lock_type = typename LockingPolicy::lock_type; 62 | 63 | public: 64 | /// Default constructor, we only want this to be available 65 | /// i.e. the unique_pool to be default constructible if the 66 | /// value_type we build is default constructible. 67 | /// 68 | /// This means that we only want 69 | /// std::is_default_constructible>::value to 70 | /// be true if the type T is default constructible. 71 | /// 72 | /// Unfortunately this does not work if we don't do the 73 | /// template magic seen below. What we do there is to use 74 | /// SFINAE to disable the default constructor for non default 75 | /// constructible types. 76 | /// 77 | /// It looks quite ugly and if somebody can fix in a simpler way 78 | /// please do :) 79 | template ::value, 81 | uint8_t>::type = 0> 82 | unique_pool() : 83 | m_pool(std::make_shared( 84 | allocate_function(std::make_unique))) 85 | { 86 | } 87 | 88 | /// Create a unique_pool using a specific allocate function. 89 | /// @param allocate Allocation function 90 | unique_pool(allocate_function allocate) : 91 | m_pool(std::make_shared(std::move(allocate))) 92 | { 93 | } 94 | 95 | /// Create a unique_pool using a specific allocate function and 96 | /// recycle function. 97 | /// @param allocate Allocation function 98 | /// @param recycle Recycle function 99 | unique_pool(allocate_function allocate, recycle_function recycle) : 100 | m_pool(std::make_shared(std::move(allocate), std::move(recycle))) 101 | { 102 | } 103 | 104 | /// Copy constructor 105 | unique_pool(const unique_pool& other) : 106 | m_pool(std::make_shared(*other.m_pool)) 107 | { 108 | } 109 | 110 | /// Move constructor 111 | unique_pool(unique_pool&& other) : m_pool(std::move(other.m_pool)) 112 | { 113 | assert(m_pool); 114 | } 115 | 116 | /// Copy assignment 117 | unique_pool& operator=(const unique_pool& other) 118 | { 119 | unique_pool tmp(other); 120 | std::swap(*this, tmp); 121 | return *this; 122 | } 123 | 124 | /// Move assignment 125 | unique_pool& operator=(unique_pool&& other) 126 | { 127 | m_pool = std::move(other.m_pool); 128 | return *this; 129 | } 130 | 131 | /// @returns the number of unused resources 132 | std::size_t unused_resources() const 133 | { 134 | assert(m_pool); 135 | return m_pool->unused_resources(); 136 | } 137 | 138 | /// Frees all unused resources 139 | void free_unused() 140 | { 141 | assert(m_pool); 142 | m_pool->free_unused(); 143 | } 144 | 145 | /// @return A resource from the pool. 146 | pool_ptr allocate() 147 | { 148 | assert(m_pool); 149 | return m_pool->allocate(); 150 | } 151 | 152 | private: 153 | /// The actual pool implementation. We use the 154 | /// enable_shared_from_this helper to make sure we can pass a 155 | /// "back-pointer" to the pooled objects. The idea behind this 156 | /// is that we need objects to be able to add themselves back 157 | /// into the pool once they go out of scope. 158 | struct impl : public std::enable_shared_from_this 159 | { 160 | /// @copydoc unique_pool::unique_pool(allocate_function) 161 | impl(allocate_function allocate) : m_allocate(std::move(allocate)) 162 | { 163 | assert(m_allocate); 164 | } 165 | 166 | /// @copydoc unique_pool::unique_pool(allocate_function, 167 | /// recycle_function) 168 | impl(allocate_function allocate, recycle_function recycle) : 169 | m_allocate(std::move(allocate)), m_recycle(std::move(recycle)) 170 | { 171 | assert(m_allocate); 172 | assert(m_recycle); 173 | } 174 | 175 | /// Copy constructor 176 | impl(const impl& other) : 177 | std::enable_shared_from_this(other), 178 | m_allocate(other.m_allocate), m_recycle(other.m_recycle) 179 | { 180 | std::size_t size = other.unused_resources(); 181 | for (std::size_t i = 0; i < size; ++i) 182 | { 183 | m_free_list.push_back(m_allocate()); 184 | } 185 | } 186 | 187 | /// Move constructor 188 | impl(impl&& other) : 189 | std::enable_shared_from_this(other), 190 | m_allocate(std::move(other.m_allocate)), 191 | m_recycle(std::move(other.m_recycle)), 192 | m_free_list(std::move(other.m_free_list)) 193 | { 194 | } 195 | 196 | /// Copy assignment 197 | impl& operator=(const impl& other) 198 | { 199 | impl tmp(other); 200 | std::swap(*this, tmp); 201 | return *this; 202 | } 203 | 204 | /// Move assignment 205 | impl& operator=(impl&& other) 206 | { 207 | m_allocate = std::move(other.m_allocate); 208 | m_recycle = std::move(other.m_recycle); 209 | m_free_list = std::move(other.m_free_list); 210 | return *this; 211 | } 212 | 213 | /// Allocate a new value from the pool 214 | pool_ptr allocate() 215 | { 216 | value_ptr resource; 217 | 218 | { 219 | lock_type lock(m_mutex); 220 | 221 | if (m_free_list.size() > 0) 222 | { 223 | resource = std::move(m_free_list.back()); 224 | m_free_list.pop_back(); 225 | } 226 | } 227 | 228 | if (!resource) 229 | { 230 | assert(m_allocate); 231 | resource = m_allocate(); 232 | } 233 | 234 | auto pool = impl::shared_from_this(); 235 | 236 | // Here we create a std::unique_ptr with a naked 237 | // pointer to the resource and a custom deleter 238 | // object. The custom deleter object stores two 239 | // things: 240 | // 241 | // 1. A std::weak_ptr to the pool (used when we 242 | // need to put the resource back in the pool). If 243 | // the pool dies before the resource then we can 244 | // detect this with the weak_ptr and no try to 245 | // access it. 246 | // 247 | // 2. A std::unique_ptr that points to the actual 248 | // resource and is the one actually keeping it alive. 249 | 250 | value_type* naked_ptr = resource.get(); 251 | return pool_ptr(naked_ptr, deleter(pool, std::move(resource))); 252 | } 253 | 254 | /// @copydoc unique_pool::free_unused() 255 | void free_unused() 256 | { 257 | lock_type lock(m_mutex); 258 | m_free_list.clear(); 259 | } 260 | 261 | /// @copydoc unique_pool::unused_resources() 262 | std::size_t unused_resources() const 263 | { 264 | lock_type lock(m_mutex); 265 | return m_free_list.size(); 266 | } 267 | 268 | /// This function called when a resource should be added 269 | /// back into the pool 270 | void recycle(value_ptr resource) 271 | { 272 | if (m_recycle) 273 | { 274 | m_recycle(resource); 275 | } 276 | 277 | lock_type lock(m_mutex); 278 | m_free_list.push_back(std::move(resource)); 279 | } 280 | 281 | private: 282 | /// The allocator to use 283 | allocate_function m_allocate; 284 | 285 | /// The recycle function 286 | recycle_function m_recycle; 287 | 288 | /// Stores all the free resources 289 | std::list m_free_list; 290 | 291 | /// Mutex used to coordinate access to the pool. We had to 292 | /// make it mutable as we have to lock in the 293 | /// unused_resources() function. Otherwise we can have a 294 | /// race condition on the size it returns. I.e. if one 295 | /// threads releases a resource into the free list while 296 | /// another tries to read its size. 297 | mutable mutex_type m_mutex; 298 | }; 299 | 300 | /// The custom deleter object used by the std::unique_ptr 301 | /// to de-allocate the object if the pool goes out of 302 | /// scope. When a std::unique_ptr wants to de-allocate the 303 | /// object contained it will call the operator() define here. 304 | struct deleter 305 | { 306 | /// Constructor 307 | deleter() = default; 308 | 309 | /// @param pool A weak_ptr to the pool 310 | /// @param resource The owning unique_ptr 311 | deleter(const std::weak_ptr& pool, 312 | std::unique_ptr resource) : 313 | m_pool(pool), 314 | m_resource(std::move(resource)) 315 | { 316 | assert(!m_pool.expired()); 317 | assert(m_resource); 318 | } 319 | 320 | /// Call operator called by std::unique_ptr when 321 | /// de-allocating the object. 322 | void operator()(value_type*) 323 | { 324 | // Place the resource in the free list 325 | auto pool = m_pool.lock(); 326 | 327 | if (pool) 328 | { 329 | pool->recycle(std::move(m_resource)); 330 | } 331 | } 332 | 333 | // Pointer to the pool needed for recycling 334 | std::weak_ptr m_pool; 335 | 336 | // The resource object 337 | std::unique_ptr m_resource; 338 | }; 339 | 340 | private: 341 | // The pool impl 342 | std::shared_ptr m_pool; 343 | }; 344 | } // namespace recycle 345 | -------------------------------------------------------------------------------- /test/recycle_tests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Steinwurf ApS 2014. 2 | // All Rights Reserved 3 | // 4 | // Distributed under the "BSD License". See the accompanying LICENSE.rst file. 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | GTEST_API_ int main(int argc, char** argv) 12 | { 13 | srand(static_cast(time(0))); 14 | 15 | testing::InitGoogleTest(&argc, argv); 16 | return RUN_ALL_TESTS(); 17 | } -------------------------------------------------------------------------------- /test/src/test_no_locking_policy.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Steinwurf ApS 2014. 2 | // All Rights Reserved 3 | // 4 | // Distributed under the "BSD License". See the accompanying LICENSE.rst file. 5 | 6 | #include 7 | 8 | #include 9 | 10 | TEST(test_no_locking_policy, empty) 11 | { 12 | recycle::no_locking_policy policy; 13 | (void)policy; 14 | } 15 | -------------------------------------------------------------------------------- /test/src/test_resource_pool.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Steinwurf ApS 2014. 2 | // All Rights Reserved 3 | // 4 | // Distributed under the "BSD License". See the accompanying LICENSE.rst file. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | // Put tests classes in an anonymous namespace to avoid violations of 18 | // ODF (one-definition-rule) in other translation units 19 | namespace 20 | { 21 | // Default constructible dummy object 22 | struct dummy_one 23 | { 24 | dummy_one() 25 | { 26 | ++m_count; 27 | } 28 | 29 | ~dummy_one() 30 | { 31 | --m_count; 32 | } 33 | 34 | // Counter which will check how many object have been allocate 35 | // and deallocated 36 | static int32_t m_count; 37 | }; 38 | 39 | int32_t dummy_one::m_count = 0; 40 | 41 | std::shared_ptr make_dummy_one() 42 | { 43 | return std::make_shared(); 44 | } 45 | 46 | // Non Default constructible dummy object 47 | struct dummy_two 48 | { 49 | dummy_two(std::size_t) 50 | { 51 | ++m_count; 52 | } 53 | 54 | ~dummy_two() 55 | { 56 | --m_count; 57 | } 58 | 59 | static int32_t m_count; 60 | }; 61 | 62 | int32_t dummy_two::m_count = 0; 63 | 64 | std::shared_ptr make_dummy_two(std::size_t v) 65 | { 66 | return std::make_shared(v); 67 | } 68 | 69 | // enable_shared_from_this dummy object 70 | struct dummy_three : std::enable_shared_from_this 71 | { 72 | 73 | dummy_three() 74 | { 75 | ++m_count; 76 | } 77 | 78 | ~dummy_three() 79 | { 80 | --m_count; 81 | } 82 | 83 | // Counter which will check how many object have been allocate 84 | // and deallocated 85 | static int32_t m_count; 86 | }; 87 | 88 | int32_t dummy_three::m_count = 0; 89 | } 90 | 91 | /// Test that our resource pool is a regular type. We are not 92 | /// implementing equality or less than here, but maybe we could. 93 | namespace 94 | { 95 | /// This code checks whether a type is regular or not. See the 96 | /// Eric Niebler's talk from C++Now 97 | /// 2014. http://youtu.be/zgOF4NrQllo 98 | // clang-format off 99 | template 100 | struct is_regular 101 | : std::integral_constant::value && 102 | std::is_copy_constructible::value && 103 | std::is_move_constructible::value && 104 | std::is_copy_assignable::value && 105 | std::is_move_assignable::value> 106 | { 107 | }; 108 | // clang-format on 109 | } 110 | 111 | TEST(test_shared_pool, regular_type) 112 | { 113 | EXPECT_TRUE(is_regular>::value); 114 | EXPECT_FALSE(is_regular>::value); 115 | } 116 | 117 | /// Test the basic API construct and free some objects 118 | TEST(test_shared_pool, api) 119 | { 120 | { 121 | recycle::shared_pool pool; 122 | 123 | EXPECT_EQ(pool.unused_resources(), 0U); 124 | 125 | { 126 | auto d1 = pool.allocate(); 127 | EXPECT_EQ(pool.unused_resources(), 0U); 128 | } 129 | 130 | EXPECT_EQ(pool.unused_resources(), 1U); 131 | 132 | auto d2 = pool.allocate(); 133 | 134 | EXPECT_EQ(pool.unused_resources(), 0U); 135 | 136 | auto d3 = pool.allocate(); 137 | 138 | EXPECT_EQ(pool.unused_resources(), 0U); 139 | EXPECT_EQ(dummy_one::m_count, 2); 140 | 141 | { 142 | auto d4 = pool.allocate(); 143 | EXPECT_EQ(pool.unused_resources(), 0U); 144 | } 145 | 146 | EXPECT_EQ(pool.unused_resources(), 1U); 147 | 148 | pool.free_unused(); 149 | 150 | EXPECT_EQ(pool.unused_resources(), 0U); 151 | } 152 | 153 | EXPECT_EQ(dummy_one::m_count, 0); 154 | } 155 | 156 | /// Test the pool works with std::bind 157 | TEST(test_shared_pool, bind) 158 | { 159 | { 160 | recycle::shared_pool pool_one(std::bind(make_dummy_one)); 161 | 162 | recycle::shared_pool pool_two(std::bind(make_dummy_two, 4U)); 163 | 164 | auto o1 = pool_one.allocate(); 165 | auto o2 = pool_two.allocate(); 166 | 167 | EXPECT_EQ(dummy_one::m_count, 1); 168 | EXPECT_EQ(dummy_two::m_count, 1); 169 | } 170 | 171 | EXPECT_EQ(dummy_one::m_count, 0); 172 | EXPECT_EQ(dummy_two::m_count, 0); 173 | } 174 | 175 | /// Test that the pool works for non default constructable objects, if 176 | /// we provide the allocator 177 | TEST(test_shared_pool, non_default_constructable) 178 | { 179 | { 180 | recycle::shared_pool pool(std::bind(make_dummy_two, 4U)); 181 | 182 | auto o1 = pool.allocate(); 183 | auto o2 = pool.allocate(); 184 | 185 | EXPECT_EQ(dummy_two::m_count, 2); 186 | } 187 | 188 | EXPECT_EQ(dummy_two::m_count, 0); 189 | 190 | { 191 | // clang-format off 192 | auto make = []() -> std::shared_ptr 193 | { 194 | return std::make_shared(3U); 195 | }; 196 | // clang-format on 197 | 198 | recycle::shared_pool pool(make); 199 | 200 | auto o1 = pool.allocate(); 201 | auto o2 = pool.allocate(); 202 | 203 | EXPECT_EQ(dummy_two::m_count, 2); 204 | } 205 | 206 | EXPECT_EQ(dummy_two::m_count, 0); 207 | } 208 | 209 | /// Test that the pool works for non constructable objects, even if 210 | /// we do not provide the allocator 211 | TEST(test_shared_pool, default_constructable) 212 | { 213 | { 214 | recycle::shared_pool pool; 215 | 216 | auto o1 = pool.allocate(); 217 | auto o2 = pool.allocate(); 218 | 219 | EXPECT_EQ(dummy_one::m_count, 2); 220 | } 221 | 222 | EXPECT_EQ(dummy_one::m_count, 0); 223 | } 224 | 225 | /// Test that everything works even if the pool dies before the 226 | /// objects allocated 227 | TEST(test_shared_pool, pool_die_before_object) 228 | { 229 | { 230 | std::shared_ptr d1; 231 | std::shared_ptr d2; 232 | std::shared_ptr d3; 233 | 234 | { 235 | recycle::shared_pool pool; 236 | 237 | d1 = pool.allocate(); 238 | d2 = pool.allocate(); 239 | d3 = pool.allocate(); 240 | 241 | EXPECT_EQ(dummy_one::m_count, 3); 242 | } 243 | 244 | EXPECT_EQ(dummy_one::m_count, 3); 245 | } 246 | 247 | EXPECT_EQ(dummy_one::m_count, 0); 248 | } 249 | 250 | /// Test that the recycle functionality works 251 | TEST(test_shared_pool, recycle) 252 | { 253 | std::size_t recycled = 0; 254 | 255 | // clang-format off 256 | auto recycle = [&recycled](std::shared_ptr o) 257 | { 258 | EXPECT_TRUE((bool)o); 259 | ++recycled; 260 | }; 261 | 262 | auto make = []() -> std::shared_ptr 263 | { 264 | return std::make_shared(3U); 265 | }; 266 | // clang-format on 267 | 268 | recycle::shared_pool pool(make, recycle); 269 | 270 | auto o1 = pool.allocate(); 271 | o1.reset(); 272 | 273 | EXPECT_EQ(recycled, 1U); 274 | } 275 | 276 | /// Test that copying the shared_pool works as expected. 277 | /// 278 | /// For a type to be regular then: 279 | /// 280 | /// T a = b; assert(a == b); 281 | /// T a; a = b; <-> T a = b; 282 | /// T a = c; T b = c; a = d; assert(b == c); 283 | /// T a = c; T b = c; zap(a); assert(b == c && a != b); 284 | /// 285 | TEST(test_shared_pool, copy_constructor) 286 | { 287 | recycle::shared_pool pool; 288 | 289 | auto o1 = pool.allocate(); 290 | auto o2 = pool.allocate(); 291 | 292 | o1.reset(); 293 | 294 | recycle::shared_pool new_pool(pool); 295 | 296 | EXPECT_EQ(pool.unused_resources(), 1U); 297 | EXPECT_EQ(new_pool.unused_resources(), 1U); 298 | 299 | o2.reset(); 300 | 301 | EXPECT_EQ(pool.unused_resources(), 2U); 302 | EXPECT_EQ(new_pool.unused_resources(), 1U); 303 | 304 | EXPECT_EQ(dummy_one::m_count, 3); 305 | 306 | pool.free_unused(); 307 | new_pool.free_unused(); 308 | 309 | EXPECT_EQ(dummy_one::m_count, 0); 310 | } 311 | 312 | /// Test copy assignment works 313 | TEST(test_shared_pool, copy_assignment) 314 | { 315 | recycle::shared_pool pool; 316 | 317 | auto o1 = pool.allocate(); 318 | auto o2 = pool.allocate(); 319 | 320 | o1.reset(); 321 | 322 | recycle::shared_pool new_pool; 323 | new_pool = pool; 324 | 325 | EXPECT_EQ(dummy_one::m_count, 3); 326 | auto o3 = new_pool.allocate(); 327 | EXPECT_EQ(dummy_one::m_count, 3); 328 | } 329 | 330 | /// Test move constructor 331 | TEST(test_shared_pool, move_constructor) 332 | { 333 | recycle::shared_pool pool; 334 | 335 | auto o1 = pool.allocate(); 336 | auto o2 = pool.allocate(); 337 | 338 | o1.reset(); 339 | 340 | recycle::shared_pool new_pool(std::move(pool)); 341 | 342 | o2.reset(); 343 | EXPECT_EQ(new_pool.unused_resources(), 2U); 344 | } 345 | 346 | /// Test move assignment 347 | TEST(test_shared_pool, move_assignment) 348 | { 349 | recycle::shared_pool pool; 350 | 351 | auto o1 = pool.allocate(); 352 | auto o2 = pool.allocate(); 353 | 354 | o1.reset(); 355 | 356 | recycle::shared_pool new_pool; 357 | new_pool = std::move(pool); 358 | 359 | o2.reset(); 360 | 361 | EXPECT_EQ(new_pool.unused_resources(), 2U); 362 | } 363 | 364 | /// Test that copy assignment works when we copy from an object with 365 | /// recycle functionality 366 | TEST(test_shared_pool, copy_recycle) 367 | { 368 | std::size_t recycled = 0; 369 | 370 | // clang-format off 371 | auto recycle = [&recycled](std::shared_ptr o) 372 | { 373 | EXPECT_TRUE((bool)o); 374 | ++recycled; 375 | }; 376 | 377 | auto make = []() -> std::shared_ptr 378 | { 379 | return std::make_shared(3U); 380 | }; 381 | // clang-format on 382 | 383 | recycle::shared_pool pool(make, recycle); 384 | recycle::shared_pool new_pool = pool; 385 | 386 | EXPECT_EQ(pool.unused_resources(), 0U); 387 | EXPECT_EQ(new_pool.unused_resources(), 0U); 388 | 389 | auto o1 = new_pool.allocate(); 390 | 391 | EXPECT_EQ(dummy_two::m_count, 1); 392 | 393 | o1.reset(); 394 | EXPECT_EQ(recycled, 1U); 395 | 396 | new_pool.free_unused(); 397 | 398 | EXPECT_EQ(dummy_two::m_count, 0); 399 | } 400 | 401 | /// Test that we are thread safe 402 | namespace 403 | { 404 | struct lock_policy 405 | { 406 | using mutex_type = std::mutex; 407 | using lock_type = std::lock_guard; 408 | }; 409 | } 410 | 411 | TEST(test_shared_pool, thread) 412 | { 413 | std::size_t recycled = 0; 414 | 415 | // clang-format off 416 | auto recycle = [&recycled](std::shared_ptr o) 417 | { 418 | EXPECT_TRUE((bool)o); 419 | ++recycled; 420 | }; 421 | 422 | auto make = []() -> std::shared_ptr 423 | { 424 | return std::make_shared(3U); 425 | }; 426 | // clang-format on 427 | 428 | // The pool we will use 429 | using pool_type = recycle::shared_pool; 430 | 431 | pool_type pool(make, recycle); 432 | 433 | // Lambda the threads will execute captures a reference to the pool 434 | // so they will all operate on the same pool concurrently 435 | // clang-format off 436 | auto run = [&pool]() 437 | { 438 | { 439 | auto a1 = pool.allocate(); 440 | } 441 | 442 | auto a2 = pool.allocate(); 443 | auto a3 = pool.allocate(); 444 | 445 | { 446 | auto a4 = pool.allocate(); 447 | } 448 | 449 | pool_type new_pool = pool; 450 | 451 | auto b1 = new_pool.allocate(); 452 | auto b2 = new_pool.allocate(); 453 | 454 | pool.free_unused(); 455 | }; 456 | // clang-format on 457 | 458 | const std::size_t number_threads = 8; 459 | std::thread t[number_threads]; 460 | 461 | // Launch a group of threads 462 | for (std::size_t i = 0; i < number_threads; ++i) 463 | { 464 | t[i] = std::thread(run); 465 | } 466 | 467 | // Join the threads with the main thread 468 | for (std::size_t i = 0; i < number_threads; ++i) 469 | { 470 | t[i].join(); 471 | } 472 | } 473 | 474 | /// Test that the pool works for enable_shared_from_this objects, even if 475 | /// we do not provide the allocator 476 | TEST(test_shared_pool, enable_shared_from_this) 477 | { 478 | { 479 | recycle::shared_pool pool; 480 | 481 | auto o1 = pool.allocate(); 482 | EXPECT_EQ(o1.use_count(), 1); 483 | 484 | EXPECT_EQ(dummy_three::m_count, 1); 485 | } 486 | 487 | EXPECT_EQ(dummy_three::m_count, 0); 488 | } 489 | -------------------------------------------------------------------------------- /test/src/test_unique_pool.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Steinwurf ApS 2014. 2 | // All Rights Reserved 3 | // 4 | // Distributed under the "BSD License". See the accompanying LICENSE.rst file. 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | // Put tests classes in an anonymous namespace to avoid violations of 18 | // ODF (one-definition-rule) in other translation units 19 | namespace 20 | { 21 | // Default constructible dummy object 22 | struct dummy_one 23 | { 24 | dummy_one() 25 | { 26 | ++m_count; 27 | } 28 | 29 | ~dummy_one() 30 | { 31 | --m_count; 32 | } 33 | 34 | // Counter which will check how many object have been allocate 35 | // and deallocated 36 | static int32_t m_count; 37 | }; 38 | 39 | int32_t dummy_one::m_count = 0; 40 | 41 | std::unique_ptr make_dummy_one() 42 | { 43 | return std::make_unique(); 44 | } 45 | 46 | // Non Default constructible dummy object 47 | struct dummy_two 48 | { 49 | dummy_two(std::size_t) 50 | { 51 | ++m_count; 52 | } 53 | 54 | ~dummy_two() 55 | { 56 | --m_count; 57 | } 58 | 59 | static int32_t m_count; 60 | }; 61 | 62 | int32_t dummy_two::m_count = 0; 63 | 64 | std::unique_ptr make_dummy_two(std::size_t v) 65 | { 66 | return std::make_unique(v); 67 | } 68 | 69 | // enable_shared_from_this dummy object 70 | struct dummy_three : std::enable_shared_from_this 71 | { 72 | 73 | dummy_three() 74 | { 75 | ++m_count; 76 | } 77 | 78 | ~dummy_three() 79 | { 80 | --m_count; 81 | } 82 | 83 | // Counter which will check how many object have been allocate 84 | // and deallocated 85 | static int32_t m_count; 86 | }; 87 | 88 | int32_t dummy_three::m_count = 0; 89 | } 90 | 91 | /// Test that our resource pool is a regular type. We are not 92 | /// implementing equality or less than here, but maybe we could. 93 | namespace 94 | { 95 | /// This code checks whether a type is regular or not. See the 96 | /// Eric Niebler's talk from C++Now 97 | /// 2014. http://youtu.be/zgOF4NrQllo 98 | template 99 | struct is_regular 100 | : std::integral_constant::value && 101 | std::is_copy_constructible::value && 102 | std::is_move_constructible::value && 103 | std::is_copy_assignable::value && 104 | std::is_move_assignable::value> 105 | { 106 | }; 107 | } 108 | 109 | TEST(test_unique_pool, regular_type) 110 | { 111 | EXPECT_TRUE(is_regular>::value); 112 | EXPECT_FALSE(is_regular>::value); 113 | } 114 | 115 | /// Test the basic API construct and free some objects 116 | TEST(test_unique_pool, api) 117 | { 118 | { 119 | recycle::unique_pool pool; 120 | 121 | EXPECT_EQ(pool.unused_resources(), 0U); 122 | 123 | { 124 | auto d1 = pool.allocate(); 125 | EXPECT_EQ(pool.unused_resources(), 0U); 126 | } 127 | 128 | EXPECT_EQ(pool.unused_resources(), 1U); 129 | 130 | auto d2 = pool.allocate(); 131 | 132 | EXPECT_EQ(pool.unused_resources(), 0U); 133 | 134 | auto d3 = pool.allocate(); 135 | 136 | EXPECT_EQ(pool.unused_resources(), 0U); 137 | EXPECT_EQ(dummy_one::m_count, 2); 138 | 139 | { 140 | auto d4 = pool.allocate(); 141 | EXPECT_EQ(pool.unused_resources(), 0U); 142 | } 143 | 144 | EXPECT_EQ(pool.unused_resources(), 1U); 145 | 146 | pool.free_unused(); 147 | 148 | EXPECT_EQ(pool.unused_resources(), 0U); 149 | } 150 | 151 | EXPECT_EQ(dummy_one::m_count, 0); 152 | } 153 | 154 | /// Test the pool works with std::bind 155 | TEST(test_unique_pool, bind) 156 | { 157 | { 158 | recycle::unique_pool pool_one(std::bind(make_dummy_one)); 159 | 160 | recycle::unique_pool pool_two(std::bind(make_dummy_two, 4U)); 161 | 162 | auto o1 = pool_one.allocate(); 163 | auto o2 = pool_two.allocate(); 164 | 165 | EXPECT_EQ(dummy_one::m_count, 1); 166 | EXPECT_EQ(dummy_two::m_count, 1); 167 | } 168 | 169 | EXPECT_EQ(dummy_one::m_count, 0); 170 | EXPECT_EQ(dummy_two::m_count, 0); 171 | } 172 | 173 | /// Test that the pool works for non default constructable objects, if 174 | /// we provide the allocator 175 | TEST(test_unique_pool, non_default_constructable) 176 | { 177 | { 178 | recycle::unique_pool pool(std::bind(make_dummy_two, 4U)); 179 | 180 | auto o1 = pool.allocate(); 181 | auto o2 = pool.allocate(); 182 | 183 | EXPECT_EQ(dummy_two::m_count, 2); 184 | } 185 | 186 | EXPECT_EQ(dummy_two::m_count, 0); 187 | 188 | { 189 | auto make = []() -> std::unique_ptr 190 | { return std::make_unique(3U); }; 191 | 192 | recycle::unique_pool pool(make); 193 | 194 | auto o1 = pool.allocate(); 195 | auto o2 = pool.allocate(); 196 | 197 | EXPECT_EQ(dummy_two::m_count, 2); 198 | } 199 | 200 | EXPECT_EQ(dummy_two::m_count, 0); 201 | } 202 | 203 | /// Test that the pool works for non constructable objects, even if 204 | /// we do not provide the allocator 205 | TEST(test_unique_pool, default_constructable) 206 | { 207 | { 208 | recycle::unique_pool pool; 209 | 210 | auto o1 = pool.allocate(); 211 | auto o2 = pool.allocate(); 212 | 213 | EXPECT_EQ(dummy_one::m_count, 2); 214 | } 215 | 216 | EXPECT_EQ(dummy_one::m_count, 0); 217 | } 218 | 219 | /// Test that everything works even if the pool dies before the 220 | /// objects allocated 221 | TEST(test_unique_pool, pool_die_before_object) 222 | { 223 | { 224 | recycle::unique_pool::pool_ptr d1; 225 | recycle::unique_pool::pool_ptr d2; 226 | recycle::unique_pool::pool_ptr d3; 227 | 228 | { 229 | recycle::unique_pool pool; 230 | 231 | d1 = pool.allocate(); 232 | d2 = pool.allocate(); 233 | d3 = pool.allocate(); 234 | 235 | EXPECT_EQ(dummy_one::m_count, 3); 236 | } 237 | 238 | EXPECT_EQ(dummy_one::m_count, 3); 239 | } 240 | 241 | EXPECT_EQ(dummy_one::m_count, 0); 242 | } 243 | 244 | /// Test that the recycle functionality works 245 | TEST(test_unique_pool, recycle) 246 | { 247 | std::size_t recycled = 0; 248 | 249 | auto recycle = [&recycled](std::unique_ptr& o) 250 | { 251 | EXPECT_TRUE((bool)o); 252 | ++recycled; 253 | }; 254 | 255 | auto make = []() -> std::unique_ptr 256 | { return std::make_unique(3U); }; 257 | 258 | recycle::unique_pool pool(make, recycle); 259 | 260 | auto o1 = pool.allocate(); 261 | o1.reset(); 262 | 263 | EXPECT_EQ(recycled, 1U); 264 | } 265 | 266 | /// Test that copying the shared_pool works as expected. 267 | /// 268 | /// For a type to be regular then: 269 | /// 270 | /// T a = b; assert(a == b); 271 | /// T a; a = b; <-> T a = b; 272 | /// T a = c; T b = c; a = d; assert(b == c); 273 | /// T a = c; T b = c; zap(a); assert(b == c && a != b); 274 | /// 275 | TEST(test_unique_pool, copy_constructor) 276 | { 277 | recycle::unique_pool pool; 278 | 279 | auto o1 = pool.allocate(); 280 | auto o2 = pool.allocate(); 281 | 282 | o1.reset(); 283 | 284 | recycle::unique_pool new_pool(pool); 285 | 286 | EXPECT_EQ(pool.unused_resources(), 1U); 287 | EXPECT_EQ(new_pool.unused_resources(), 1U); 288 | 289 | o2.reset(); 290 | 291 | EXPECT_EQ(pool.unused_resources(), 2U); 292 | EXPECT_EQ(new_pool.unused_resources(), 1U); 293 | 294 | EXPECT_EQ(dummy_one::m_count, 3); 295 | 296 | pool.free_unused(); 297 | new_pool.free_unused(); 298 | 299 | EXPECT_EQ(dummy_one::m_count, 0); 300 | } 301 | 302 | /// Test copy assignment works 303 | TEST(test_unique_pool, copy_assignment) 304 | { 305 | recycle::unique_pool pool; 306 | 307 | auto o1 = pool.allocate(); 308 | auto o2 = pool.allocate(); 309 | 310 | o1.reset(); 311 | 312 | recycle::unique_pool new_pool; 313 | new_pool = pool; 314 | 315 | EXPECT_EQ(dummy_one::m_count, 3); 316 | auto o3 = new_pool.allocate(); 317 | EXPECT_EQ(dummy_one::m_count, 3); 318 | } 319 | 320 | /// Test move constructor 321 | TEST(test_unique_pool, move_constructor) 322 | { 323 | recycle::unique_pool pool; 324 | 325 | auto o1 = pool.allocate(); 326 | auto o2 = pool.allocate(); 327 | 328 | o1.reset(); 329 | 330 | recycle::unique_pool new_pool(std::move(pool)); 331 | 332 | o2.reset(); 333 | EXPECT_EQ(new_pool.unused_resources(), 2U); 334 | } 335 | 336 | /// Test move assignment 337 | TEST(test_unique_pool, move_assignment) 338 | { 339 | recycle::unique_pool pool; 340 | 341 | auto o1 = pool.allocate(); 342 | auto o2 = pool.allocate(); 343 | 344 | o1.reset(); 345 | 346 | recycle::unique_pool new_pool; 347 | new_pool = std::move(pool); 348 | 349 | o2.reset(); 350 | 351 | EXPECT_EQ(new_pool.unused_resources(), 2U); 352 | } 353 | 354 | /// Test that copy assignment works when we copy from an object with 355 | /// recycle functionality 356 | TEST(test_unique_pool, copy_recycle) 357 | { 358 | std::size_t recycled = 0; 359 | 360 | auto recycle = [&recycled](std::unique_ptr& o) 361 | { 362 | EXPECT_TRUE((bool)o); 363 | ++recycled; 364 | }; 365 | 366 | auto make = []() -> std::unique_ptr 367 | { return std::make_unique(3U); }; 368 | 369 | recycle::unique_pool pool(make, recycle); 370 | recycle::unique_pool new_pool = pool; 371 | 372 | EXPECT_EQ(pool.unused_resources(), 0U); 373 | EXPECT_EQ(new_pool.unused_resources(), 0U); 374 | 375 | auto o1 = new_pool.allocate(); 376 | 377 | EXPECT_EQ(dummy_two::m_count, 1); 378 | 379 | o1.reset(); 380 | EXPECT_EQ(recycled, 1U); 381 | 382 | new_pool.free_unused(); 383 | 384 | EXPECT_EQ(dummy_two::m_count, 0); 385 | } 386 | 387 | /// Test that we are thread safe 388 | namespace 389 | { 390 | struct lock_policy 391 | { 392 | using mutex_type = std::mutex; 393 | using lock_type = std::lock_guard; 394 | }; 395 | } 396 | 397 | TEST(test_unique_pool, thread) 398 | { 399 | std::size_t recycled = 0; 400 | 401 | auto recycle = [&recycled](std::unique_ptr& o) 402 | { 403 | EXPECT_TRUE((bool)o); 404 | ++recycled; 405 | }; 406 | 407 | auto make = []() -> std::unique_ptr 408 | { return std::make_unique(3U); }; 409 | 410 | // The pool we will use 411 | using pool_type = recycle::unique_pool; 412 | 413 | pool_type pool(make, recycle); 414 | 415 | // Lambda the threads will execute captures a reference to the pool 416 | // so they will all operate on the same pool concurrently 417 | auto run = [&pool]() 418 | { 419 | { 420 | auto a1 = pool.allocate(); 421 | } 422 | 423 | auto a2 = pool.allocate(); 424 | auto a3 = pool.allocate(); 425 | 426 | { 427 | auto a4 = pool.allocate(); 428 | } 429 | 430 | pool_type new_pool = pool; 431 | 432 | auto b1 = new_pool.allocate(); 433 | auto b2 = new_pool.allocate(); 434 | 435 | pool.free_unused(); 436 | }; 437 | 438 | const std::size_t number_threads = 8; 439 | std::thread t[number_threads]; 440 | 441 | // Launch a group of threads 442 | for (std::size_t i = 0; i < number_threads; ++i) 443 | { 444 | t[i] = std::thread(run); 445 | } 446 | 447 | // Join the threads with the main thread 448 | for (std::size_t i = 0; i < number_threads; ++i) 449 | { 450 | t[i].join(); 451 | } 452 | } 453 | 454 | /// Test that the pool works for enable_shared_from_this objects, even if 455 | /// we do not provide the allocator 456 | TEST(test_unique_pool, enable_shared_from_this) 457 | { 458 | { 459 | recycle::unique_pool pool; 460 | 461 | auto o1 = pool.allocate(); 462 | 463 | EXPECT_EQ(dummy_three::m_count, 1); 464 | } 465 | 466 | EXPECT_EQ(dummy_three::m_count, 0); 467 | } 468 | -------------------------------------------------------------------------------- /test/wscript_build: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | bld.program( 4 | features="cxx test", 5 | source=["recycle_tests.cpp"] + bld.path.ant_glob("src/*.cpp"), 6 | target="recycle_tests", 7 | use=["recycle_includes", "gtest"], 8 | ) 9 | -------------------------------------------------------------------------------- /waf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steinwurf/recycle/a20d211776e30edd64a49d21157a522424352980/waf -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # encoding: utf-8 3 | 4 | APPNAME = "recycle" 5 | VERSION = "7.0.0" 6 | 7 | 8 | def configure(conf): 9 | conf.set_cxx_std(14) 10 | 11 | 12 | def build(bld): 13 | bld(name="recycle_includes", includes="./src", export_includes="./src") 14 | 15 | if bld.is_toplevel(): 16 | # Only build test when executed from the top-level wscript, 17 | # i.e. not when included as a dependency 18 | bld.recurse("test") 19 | --------------------------------------------------------------------------------