├── .gitattributes ├── .github └── workflows │ └── sanitizers.yml ├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── build └── install_catch.sh ├── small_unique_ptr.natvis ├── src └── small_unique_ptr.hpp └── test ├── CMakeLists.txt └── small_unique_ptr.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/sanitizers.yml: -------------------------------------------------------------------------------- 1 | name: sanitizers 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-24.04 8 | container: 9 | image: ubuntu:24.04 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | sanitizer: [ address, undefined ] 14 | compiler: [ 15 | { cxx: clang++-15, pkgs: clang-15 }, 16 | { cxx: clang++-16, pkgs: clang-16 }, 17 | { cxx: clang++-17, pkgs: clang-17 }, 18 | { cxx: clang++-18, pkgs: clang-18 }, 19 | { cxx: g++-11, pkgs: g++-11 }, 20 | { cxx: g++-12, pkgs: g++-12 }, 21 | { cxx: g++-13, pkgs: g++-13 }, 22 | { cxx: g++-14, pkgs: g++-14 } 23 | ] 24 | 25 | env: 26 | ASAN_OPTIONS: check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1:detect_leaks=1:detect_invalid_pointer_pairs=2 27 | UBSAN_OPTIONS: print_stacktrace=1:print_summary=1 28 | 29 | defaults: 30 | run: 31 | working-directory: ${{ github.workspace }}/build 32 | 33 | 34 | name: ${{ matrix.compiler.cxx }} -fsanitize=${{ matrix.sanitizer }} 35 | 36 | steps: 37 | - name: checkout-repo 38 | uses: actions/checkout@v4 39 | 40 | - name: setup-compiler 41 | run: apt update && apt install --allow-downgrades -y git cmake ${{ matrix.compiler.pkgs }} 42 | 43 | - name: setup-catch 44 | run: bash ./install_catch.sh -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} 45 | 46 | - name: setup-build 47 | run: cmake .. -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} -DCMAKE_CXX_FLAGS="-fsanitize=${{ matrix.sanitizer }} -g -fno-omit-frame-pointer" 48 | 49 | - name: build 50 | run: cmake --build . 51 | 52 | - name: run-tests 53 | run: ctest --output-on-failure --schedule-random -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # VSCode 2 | .vscode/ 3 | 4 | # Visual Studio 5 | .vs/ 6 | *.hint 7 | 8 | # Build results 9 | out/ 10 | build/* 11 | !build/*.sh 12 | *.exe 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | project(small_unique_ptr VERSION 0.1 LANGUAGES CXX) 4 | 5 | include(GNUInstallDirs) 6 | include(CMakePackageConfigHelpers) 7 | 8 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build") 9 | 10 | if(NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|Release|RelWithDebInfo)$") 11 | message(WARNING "The specified build type [${CMAKE_BUILD_TYPE}] is not recognized. Defaulting to Release.") 12 | set(CMAKE_BUILD_TYPE "Release") 13 | endif() 14 | 15 | if(MSVC) 16 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -permissive- -W4 -WX -wd4324 -diagnostics:caret") 17 | else() 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -Werror -pedantic-errors -g") 19 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-maybe-uninitialized") 21 | endif() 22 | endif() 23 | 24 | if(CMAKE_BUILD_TYPE MATCHES "(Release|RelWithDebInfo)") 25 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 26 | endif() 27 | 28 | add_library(small_unique_ptr INTERFACE) 29 | target_include_directories(small_unique_ptr SYSTEM INTERFACE "$" 30 | "$") 31 | 32 | target_compile_features(small_unique_ptr INTERFACE "cxx_std_20") 33 | target_compile_options(small_unique_ptr INTERFACE "$<$:-Zc:preprocessor>" "$<$:-Zc:__cplusplus>") 34 | 35 | 36 | install(TARGETS small_unique_ptr EXPORT smp-config) 37 | install(DIRECTORY "src/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" FILES_MATCHING PATTERN "*.hpp") 38 | 39 | install(EXPORT smp-config 40 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/smp" 41 | NAMESPACE smp::) 42 | 43 | write_basic_package_version_file( 44 | "${CMAKE_CURRENT_BINARY_DIR}/smp-config-version.cmake" 45 | COMPATIBILITY "SameMajorVersion") 46 | 47 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/smp-config-version.cmake" 48 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/smp") 49 | 50 | include(CTest) 51 | 52 | enable_testing() 53 | add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/test") 54 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug-msvc", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "buildRoot": "${projectDir}\\out\\build\\${name}", 8 | "installRoot": "${projectDir}\\out\\install\\${name}", 9 | "ctestCommandArgs": "--output-on-failure --schedule-random", 10 | "codeAnalysisRuleset": "${projectDir}\\core-guidelines.ruleset", 11 | "enableMicrosoftCodeAnalysis": false, 12 | "inheritEnvironments": [ "msvc_x64_x64" ] 13 | }, 14 | { 15 | "name": "x64-Release-msvc", 16 | "generator": "Ninja", 17 | "configurationType": "Release", 18 | "buildRoot": "${projectDir}\\out\\build\\${name}", 19 | "installRoot": "${projectDir}\\out\\install\\${name}", 20 | "ctestCommandArgs": "--output-on-failure --schedule-random", 21 | "codeAnalysisRuleset": "${projectDir}\\core-guidelines.ruleset", 22 | "enableMicrosoftCodeAnalysis": false, 23 | "inheritEnvironments": [ "msvc_x64_x64" ] 24 | }, 25 | { 26 | "name": "x64-RelWithDebInfo-msvc", 27 | "generator": "Ninja", 28 | "configurationType": "RelWithDebInfo", 29 | "buildRoot": "${projectDir}\\out\\build\\${name}", 30 | "installRoot": "${projectDir}\\out\\install\\${name}", 31 | "ctestCommandArgs": "--output-on-failure --schedule-random", 32 | "codeAnalysisRuleset": "${projectDir}\\core-guidelines.ruleset", 33 | "enableMicrosoftCodeAnalysis": false, 34 | "inheritEnvironments": [ "msvc_x64_x64" ] 35 | }, 36 | { 37 | "name": "x64-Debug-clang", 38 | "generator": "Ninja", 39 | "configurationType": "Debug", 40 | "buildRoot": "${projectDir}\\out\\build\\${name}", 41 | "installRoot": "${projectDir}\\out\\install\\${name}", 42 | "cmakeCommandArgs": "", 43 | "ctestCommandArgs": "--output-on-failure --schedule-random", 44 | "inheritEnvironments": [ "clang_cl_x64_x64" ] 45 | }, 46 | { 47 | "name": "x64-Release-clang", 48 | "generator": "Ninja", 49 | "configurationType": "Release", 50 | "buildRoot": "${projectDir}\\out\\build\\${name}", 51 | "installRoot": "${projectDir}\\out\\install\\${name}", 52 | "cmakeCommandArgs": "", 53 | "ctestCommandArgs": "--output-on-failure --schedule-random", 54 | "inheritEnvironments": [ "clang_cl_x64_x64" ] 55 | }, 56 | { 57 | "name": "x64-RelWithDebInfo-clang", 58 | "generator": "Ninja", 59 | "configurationType": "RelWithDebInfo", 60 | "buildRoot": "${projectDir}\\out\\build\\${name}", 61 | "installRoot": "${projectDir}\\out\\install\\${name}", 62 | "cmakeCommandArgs": "", 63 | "ctestCommandArgs": "--output-on-failure --schedule-random", 64 | "inheritEnvironments": [ "clang_cl_x64_x64" ] 65 | }, 66 | ] 67 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Krisztián Rugási 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![sanitizers](https://github.com/KRM7/small_unique_ptr/actions/workflows/sanitizers.yml/badge.svg)](https://github.com/KRM7/small_unique_ptr/actions/workflows/sanitizers.yml) 2 | 3 | ### `small_unique_ptr` 4 | 5 | A constexpr `unique_ptr` implementation in C++20 with small object optimization. 6 | 7 | ```cpp 8 | smp::small_unique_ptr p = smp::make_unique_small(); 9 | ``` 10 | 11 | Objects created with `make_unique_small` will be allocated on the stack if: 12 | 13 | - The size of `T` is not greater than the size of the internal stack buffer 14 | - The alignment of `T` is not greater than the alignment of the stack buffer 15 | - Their move constructor is `noexcept` 16 | 17 | If any of these conditions is not met, the object will be allocated dynamically. 18 | 19 | -------------------------------------------------------------------------------------------------- 20 | 21 | #### Interface 22 | 23 | The interface matches `std::unique_ptr`, except for: 24 | 25 | - There is no `Deleter` template parameter and the associated methods are not implemented 26 | (i.e. `get_deleter` and the constructors with a deleter parameter). 27 | 28 | - There is a `Size` template parameter that specifies the size of the `small_unique_ptr` object, 29 | and implicitly, the size of the internal stack buffer. The actual size of the object may be 30 | smaller than the size specified, for example if the object is always dynamically allocated. 31 | 32 | The value of `Size` may be any positive multiple of `sizeof(T*)`, but it is recommended 33 | to choose a power of 2, in order to guarantee that objects will never be dynamically 34 | allocated just because of alignment issues. 35 | 36 | - `T` can't be an incomplete type, as details of `T` are used to determine the layout 37 | of `small_unique_ptr`. 38 | 39 | - Constructors from pointers are not provided except for the `nullptr` constructor, as these 40 | would not be able to make use of the internal stack buffer. The `make_unique_small` functions 41 | should be used instead to create objects. 42 | 43 | - `release()` is not implemented due to the stack allocated case. This means that 44 | `small_unique_ptr` can't release ownership of the managed object. 45 | 46 | - There are some additional methods that don't exist for `std::unique_ptr`. These can be used 47 | to query details about the internal stack buffer and to check where objects are allocated 48 | (`is_stack_allocated`, `is_always_heap_allocated`, `stack_buffer_size`, `stack_array_size`). 49 | 50 | Everything is constexpr, but the stack buffer is not used in constant evaluated contexts, 51 | so any constexpr usage is subject to the same transient allocation requirements that a constexpr 52 | `std::unique_ptr` would be. 53 | 54 | -------------------------------------------------------------------------------------------------- 55 | 56 | #### Layout details 57 | 58 | As mentioned above, the `Size` template parameter is used to specify to size of the entire 59 | `small_unique_ptr` object, not the size of the internal stack buffer. The buffer size will 60 | be calculated from this as the size specified minus some implementation overhead. This will 61 | be the size of either 1 or 2 pointers depending on `T`. 62 | 63 | Using the default values, the size of the buffer on a 64 bit architecture will typically be: 64 | 65 | - 48 bytes for polymorphic types 66 | - 56 bytes for polymorphic types that implement a virtual `small_unique_ptr_move` method 67 | - `sizeof(T)` for non-polymophic types, with an upper limit of 56 bytes 68 | - 56 bytes for array types (rounded down to a multiple of the element size) 69 | 70 | The size of `small_unique_ptr` objects may in some cases be smaller than the size specified 71 | as the template parameter in order to avoid wasting space. 72 | Specifically, this will happen in 3 cases: 73 | 74 | - If `T` is larger than the calculated stack buffer size, there will be no buffer and 75 | `sizeof(small_unique_ptr)` will be equal to `sizeof(T*)`. 76 | - If `T` is a non-polymorphic type smaller than the calculated stack buffer size, the buffer 77 | size will be `sizeof(T)`. 78 | - If `T` is an array type, the size of the stack buffer will be rounded down to a multiple 79 | of `sizeof(T)`. 80 | 81 | The size of `small_unique_ptr` will never be greater than `Size`. 82 | 83 | Polymorphic classes that are used with `small_unique_ptr` may optionally implement a virtual 84 | `small_unique_ptr_move` method in order to increase the available buffer size. If some class 85 | in an object hierarchy declares this function, it must be properly implemented in every 86 | non-abstract derived class in the hierarchy. The signature of the function must be: 87 | 88 | `virtual void small_unique_ptr_move(void* dst) noexcept;` 89 | 90 | The implementation must move construct an object at the memory location pointed to by 91 | `dst` from the object on which the function is invoked (i.e. from `this`). 92 | The typical implementation would look like: 93 | 94 | ```cpp 95 | struct Widget 96 | { 97 | virtual void small_unique_ptr_move(void* dst) noexcept 98 | { 99 | std::construct_at(static_cast(dst), std::move(*this)); 100 | } 101 | }; 102 | ``` 103 | 104 | -------------------------------------------------------------------------------------------------- 105 | 106 | #### Usage example 107 | 108 | Example of a simplified `move_only_function` implementation with small object 109 | optimization using `small_unique_ptr`: 110 | 111 | ```cpp 112 | template 113 | class move_only_function; 114 | 115 | template 116 | class move_only_function 117 | { 118 | public: 119 | constexpr move_only_function() noexcept = default; 120 | constexpr move_only_function(std::nullptr_t) noexcept {} 121 | 122 | template 123 | requires(!std::is_same_v && std::is_invocable_r_v) 124 | constexpr move_only_function(F f) noexcept(noexcept(smp::make_unique_small>(std::move(f)))) : 125 | fptr_(smp::make_unique_small>(std::move(f))) 126 | {} 127 | 128 | template 129 | requires(!std::is_same_v && std::is_invocable_r_v) 130 | constexpr move_only_function& operator=(F f) noexcept(noexcept(smp::make_unique_small>(std::move(f)))) 131 | { 132 | fptr_ = smp::make_unique_small>(std::move(f)); 133 | return *this; 134 | } 135 | 136 | constexpr move_only_function(move_only_function&&) = default; 137 | constexpr move_only_function& operator=(move_only_function&&) = default; 138 | 139 | constexpr Ret operator()(Args... args) const 140 | { 141 | return fptr_->invoke(std::forward(args)...); 142 | } 143 | 144 | constexpr void swap(move_only_function& other) noexcept 145 | { 146 | fptr_.swap(other.fptr_); 147 | } 148 | 149 | constexpr explicit operator bool() const noexcept { return static_cast(fptr_); } 150 | 151 | private: 152 | struct ImplBase 153 | { 154 | constexpr virtual Ret invoke(Args...) = 0; 155 | constexpr virtual void small_unique_ptr_move(void* dst) noexcept = 0; 156 | constexpr virtual ~ImplBase() = default; 157 | }; 158 | 159 | template 160 | struct Impl : public ImplBase 161 | { 162 | constexpr Impl(Callable func) noexcept(std::is_nothrow_move_constructible_v) : 163 | func_(std::move(func)) 164 | {} 165 | 166 | constexpr void small_unique_ptr_move(void* dst) noexcept override 167 | { 168 | std::construct_at(static_cast(dst), std::move(*this)); 169 | } 170 | 171 | constexpr Ret invoke(Args... args) override 172 | { 173 | return std::invoke(func_, std::forward(args)...); 174 | } 175 | 176 | Callable func_; 177 | }; 178 | 179 | smp::small_unique_ptr fptr_ = nullptr; 180 | }; 181 | ``` 182 | 183 | -------------------------------------------------------------------------------------------------- 184 | 185 | #### Synopsis 186 | 187 | ```cpp 188 | #include 189 | 190 | namespace smp 191 | { 192 | template 193 | class small_unique_ptr 194 | { 195 | using element_type = std::remove_extent_t; 196 | using pointer = std::remove_extent_t*; 197 | using reference = std::remove_extent_t&; 198 | 199 | // constructors 200 | constexpr small_unique_ptr() noexcept; 201 | constexpr small_unique_ptr(std::nullptr_t) noexcept; 202 | constexpr small_unique_ptr(small_unique_ptr&& other) noexcept; 203 | 204 | template 205 | constexpr small_unique_ptr(small_unique_ptr&& other) noexcept; 206 | 207 | // assignment operators 208 | constexpr small_unique_ptr& operator=(small_unique_ptr&& other) noexcept; 209 | constexpr small_unique_ptr& operator=(std::nullptr_t) noexcept; 210 | 211 | template 212 | constexpr small_unique_ptr& operator=(small_unique_ptr&& other) noexcept; 213 | 214 | // destructor 215 | constexpr ~small_unique_ptr() noexcept; 216 | 217 | // modifiers 218 | constexpr pointer release() noexcept = delete; 219 | constexpr void reset(pointer new_data = pointer{}) noexcept; 220 | constexpr void swap(small_unique_ptr& other) noexcept; 221 | 222 | // observers 223 | constexpr pointer get() const noexcept; 224 | constexpr explicit operator bool() const noexcept; 225 | 226 | // non-array version, small_unique_ptr 227 | constexpr reference operator*() const noexcept(/*...*/) requires(!std::is_array_v); 228 | constexpr pointer operator->() const noexcept requires(!std::is_array_v); 229 | 230 | // array version, small_unique_ptr 231 | constexpr reference operator[](std::size_t idx) const requires(std::is_array_v); 232 | 233 | // stack buffer details 234 | constexpr bool is_stack_allocated() const noexcept; 235 | static constexpr bool is_always_heap_allocated() noexcept; 236 | static constexpr std::size_t stack_buffer_size() noexcept; 237 | static constexpr std::size_t stack_array_size() noexcept requires(std::is_array_v); 238 | 239 | // comparisons 240 | constexpr bool operator==(std::nullptr_t) const noexcept; 241 | constexpr std::strong_ordering operator<=>(std::nullptr_t) const noexcept; 242 | 243 | template 244 | constexpr bool operator==(const small_unique_ptr& rhs) const noexcept; 245 | 246 | template 247 | constexpr std::strong_ordering operator<=>(const small_unique_ptr& rhs) const noexcept; 248 | 249 | // streams support 250 | template 251 | friend std::basic_ostream& operator<<(std::basic_ostream& os, const small_unique_ptr& p); 252 | 253 | // swap 254 | constexpr friend void swap(small_unique_ptr& lhs, small_unique_ptr& rhs) noexcept; 255 | }; 256 | 257 | // make_unique factory functions 258 | template 259 | constexpr small_unique_ptr make_unique_small(Args&&... args) noexcept(/*...*/) requires(!std::is_array_v); 260 | 261 | template 262 | constexpr small_unique_ptr make_unique_small(std::size_t count) requires(std::is_unbounded_array_v); 263 | 264 | template 265 | constexpr small_unique_ptr make_unique_small(Args&&...) requires(std::is_bounded_array_v) = delete; 266 | 267 | // make_unique_for_overwrite factory functions 268 | template 269 | constexpr small_unique_ptr make_unique_small_for_overwrite() noexcept(/*...*/) requires(!std::is_array_v); 270 | 271 | template 272 | constexpr small_unique_ptr make_unique_small_for_overwrite(std::size_t count) requires(std::is_unbounded_array_v); 273 | 274 | template 275 | constexpr small_unique_ptr make_unique_small_for_overwrite(Args&&...) requires(std::is_bounded_array_v) = delete; 276 | 277 | } // namespace smp 278 | 279 | namespace std 280 | { 281 | // hash support 282 | template 283 | struct hash>; 284 | 285 | } // namespace std 286 | ``` 287 | -------------------------------------------------------------------------------- /build/install_catch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -e "Installing Catch2...\n" 4 | 5 | cmake --version 6 | 7 | BUILD_DIR=$(dirname $(realpath "$0"))/../build 8 | echo -e "\nThe build directory is ${BUILD_DIR}.\n" 9 | 10 | git clone --depth=1 --branch v3.6.0 https://github.com/catchorg/Catch2.git $BUILD_DIR/Catch2 11 | CATCH_BUILD_DIR=$BUILD_DIR/Catch2/build 12 | echo -e "\nThe Catch build directory is ${CATCH_BUILD_DIR}.\n" 13 | 14 | echo -e "Installing Debug configuration.\n" 15 | cmake -S $CATCH_BUILD_DIR/.. -B $CATCH_BUILD_DIR -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=OFF "$@" 16 | cmake --build $CATCH_BUILD_DIR --config Debug --parallel 17 | cmake --install $CATCH_BUILD_DIR --config Debug 18 | 19 | echo -e "Installing RelWithDebInfo configuration.\n" 20 | cmake -S $CATCH_BUILD_DIR/.. -B $CATCH_BUILD_DIR -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF "$@" 21 | cmake --build $CATCH_BUILD_DIR --config RelWithDebInfo --parallel 22 | cmake --install $CATCH_BUILD_DIR --config RelWithDebInfo 23 | 24 | echo -e "Installing Release configuration.\n" 25 | cmake -S $CATCH_BUILD_DIR/.. -B $CATCH_BUILD_DIR -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF "$@" 26 | cmake --build $CATCH_BUILD_DIR --config Release --parallel 27 | cmake --install $CATCH_BUILD_DIR --config Release 28 | -------------------------------------------------------------------------------- /small_unique_ptr.natvis: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | data_ 6 | empty 7 | {{ small_unique_ptr { *data_ } }} 8 | 9 | data_ 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/small_unique_ptr.hpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2024 Krisztián Rugási. Subject to the MIT License. */ 2 | 3 | #ifndef SMALL_UNIQUE_PTR_HPP 4 | #define SMALL_UNIQUE_PTR_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace smp::detail 19 | { 20 | template 21 | constexpr const T& min(const T& left, const T& right) noexcept { return (left < right) ? left : right; } 22 | 23 | template 24 | constexpr const T& max(const T& left, const T& right) noexcept { return (left < right) ? right : left; } 25 | 26 | template 27 | constexpr T max_pow2_factor(T n) noexcept { return n & (~n + 1); } 28 | 29 | 30 | using move_fn = void(*)(void* src, void* dst) noexcept; 31 | 32 | template 33 | void move_buffer(void* src, void* dst) noexcept(std::is_nothrow_move_constructible_v) 34 | { 35 | static_assert(!std::is_const_v && !std::is_volatile_v && !std::is_array_v); 36 | std::construct_at(static_cast(dst), std::move(*static_cast(src))); 37 | } 38 | 39 | template 40 | concept has_virtual_move = requires(std::remove_cv_t object, void* const dst) 41 | { 42 | requires std::is_polymorphic_v; 43 | { object.small_unique_ptr_move(dst) } noexcept -> std::same_as; 44 | }; 45 | 46 | 47 | template 48 | struct is_nothrow_dereferenceable : std::bool_constant())> {}; 49 | 50 | template 51 | inline constexpr bool is_nothrow_dereferenceable_v = is_nothrow_dereferenceable::value; 52 | 53 | 54 | template 55 | struct is_proper_base_of : 56 | std::conjunction, std::remove_cv_t>, 57 | std::negation, std::remove_cv_t>>> 58 | {}; 59 | 60 | template 61 | inline constexpr bool is_proper_base_of_v = is_proper_base_of::value; 62 | 63 | 64 | inline constexpr std::size_t default_small_ptr_size = 64; 65 | 66 | 67 | template 68 | struct buffer_size 69 | { 70 | private: 71 | static constexpr std::size_t dynamic_buffer_size = small_ptr_size - sizeof(T*) - !has_virtual_move * sizeof(move_fn); 72 | static constexpr std::size_t static_buffer_size = detail::min(sizeof(T), small_ptr_size - sizeof(T*)); 73 | public: 74 | static constexpr std::size_t value = std::has_virtual_destructor_v ? dynamic_buffer_size : static_buffer_size; 75 | }; 76 | 77 | template 78 | struct buffer_size 79 | { 80 | static constexpr std::size_t value = 0; 81 | }; 82 | 83 | template 84 | struct buffer_size 85 | { 86 | static constexpr std::size_t value = small_ptr_size - sizeof(T*); 87 | }; 88 | 89 | template 90 | struct buffer_size 91 | { 92 | static constexpr std::size_t value = 0; 93 | }; 94 | 95 | template 96 | inline constexpr std::size_t buffer_size_v = buffer_size::value; 97 | 98 | 99 | template 100 | struct buffer_alignment 101 | { 102 | private: 103 | static constexpr std::size_t dynamic_buffer_alignment = detail::max_pow2_factor(small_ptr_size); 104 | static constexpr std::size_t static_buffer_alignment = detail::max_pow2_factor(detail::min(alignof(T), small_ptr_size)); 105 | public: 106 | static constexpr std::size_t value = std::has_virtual_destructor_v ? dynamic_buffer_alignment : static_buffer_alignment; 107 | }; 108 | 109 | template 110 | inline constexpr std::size_t buffer_alignment_v = buffer_alignment::value; 111 | 112 | 113 | template 114 | struct is_always_heap_allocated 115 | { 116 | using U = std::remove_cv_t>; 117 | 118 | static constexpr bool value = (sizeof(U) > buffer_size_v) || 119 | (alignof(U) > buffer_alignment_v) || 120 | (!std::is_abstract_v && !std::is_nothrow_move_constructible_v); 121 | }; 122 | 123 | template 124 | inline constexpr bool is_always_heap_allocated_v = is_always_heap_allocated::value; 125 | 126 | 127 | template 128 | struct small_unique_ptr_base 129 | { 130 | using pointer = std::remove_cv_t*; 131 | using buffer_t = unsigned char[buffer_size_v]; 132 | 133 | pointer buffer(std::ptrdiff_t offset = 0) const noexcept 134 | { 135 | assert(offset <= sizeof(buffer_t)); 136 | return reinterpret_cast(static_cast(buffer_) + offset); 137 | } 138 | 139 | template 140 | void move_buffer_to(small_unique_ptr_base& dst) noexcept 141 | { 142 | move_(std::launder(buffer()), dst.buffer()); 143 | dst.move_ = move_; 144 | } 145 | 146 | constexpr bool is_stack_allocated() const noexcept 147 | { 148 | return static_cast(move_); 149 | } 150 | 151 | alignas(buffer_alignment_v) mutable buffer_t buffer_ = {}; 152 | T* data_ = nullptr; 153 | move_fn move_ = nullptr; 154 | }; 155 | 156 | template 157 | requires(is_always_heap_allocated_v) 158 | struct small_unique_ptr_base 159 | { 160 | static constexpr bool is_stack_allocated() noexcept { return false; } 161 | 162 | std::remove_extent_t* data_ = nullptr; 163 | }; 164 | 165 | template 166 | requires(!is_always_heap_allocated_v && !std::is_polymorphic_v && !std::is_array_v) 167 | struct small_unique_ptr_base 168 | { 169 | using pointer = std::remove_cv_t*; 170 | using buffer_t = std::remove_cv_t; 171 | 172 | constexpr pointer buffer(std::ptrdiff_t = 0) const noexcept 173 | { 174 | return std::addressof(buffer_); 175 | } 176 | 177 | template 178 | constexpr void move_buffer_to(small_unique_ptr_base& dst) noexcept 179 | { 180 | std::construct_at(dst.buffer(), std::move(*buffer())); 181 | } 182 | 183 | constexpr bool is_stack_allocated() const noexcept 184 | { 185 | return !std::is_constant_evaluated() && data_; 186 | } 187 | 188 | constexpr small_unique_ptr_base() noexcept {} 189 | constexpr ~small_unique_ptr_base() noexcept {} 190 | 191 | union { mutable buffer_t buffer_; }; 192 | T* data_ = nullptr; 193 | }; 194 | 195 | template 196 | requires(!is_always_heap_allocated_v && has_virtual_move) 197 | struct small_unique_ptr_base 198 | { 199 | using pointer = std::remove_cv_t*; 200 | using buffer_t = unsigned char[buffer_size_v]; 201 | 202 | pointer buffer(std::ptrdiff_t offset = 0) const noexcept 203 | { 204 | assert(offset <= sizeof(buffer_t)); 205 | return reinterpret_cast(static_cast(buffer_) + offset); 206 | } 207 | 208 | template 209 | requires(has_virtual_move) 210 | void move_buffer_to(small_unique_ptr_base& dst) noexcept 211 | { 212 | const pointer data = const_cast(data_); 213 | data->small_unique_ptr_move(dst.buffer()); 214 | } 215 | 216 | constexpr bool is_stack_allocated() const noexcept 217 | { 218 | if (std::is_constant_evaluated()) return false; 219 | 220 | auto* data = reinterpret_cast(data_); 221 | auto* buffer_first = static_cast(buffer_); 222 | auto* buffer_last = buffer_first + buffer_size_v; 223 | 224 | assert(reinterpret_cast(buffer_last) - reinterpret_cast(buffer_first) == (buffer_size_v) && 225 | "Linear address space assumed for the stack buffer."); 226 | 227 | return std::less_equal{}(buffer_first, data) && std::less{}(data, buffer_last); 228 | } 229 | 230 | alignas(buffer_alignment_v) mutable buffer_t buffer_ = {}; 231 | T* data_ = nullptr; 232 | }; 233 | 234 | template 235 | requires(!is_always_heap_allocated_v && std::is_array_v) 236 | struct small_unique_ptr_base 237 | { 238 | static constexpr std::size_t array_size = buffer_size_v / sizeof(std::remove_extent_t); 239 | 240 | using pointer = std::remove_cv_t>*; 241 | using buffer_t = std::remove_cv_t>[array_size]; 242 | 243 | constexpr pointer buffer(std::ptrdiff_t = 0) const noexcept 244 | { 245 | return static_cast(buffer_); 246 | } 247 | 248 | template 249 | void move_buffer_to(small_unique_ptr_base& dst) noexcept 250 | { 251 | std::uninitialized_move(buffer(), buffer() + array_size, dst.buffer()); 252 | } 253 | 254 | constexpr bool is_stack_allocated() const noexcept 255 | { 256 | return !std::is_constant_evaluated() && (data_ == buffer()); 257 | } 258 | 259 | constexpr small_unique_ptr_base() noexcept {} 260 | constexpr ~small_unique_ptr_base() noexcept {} 261 | 262 | union { mutable buffer_t buffer_; }; 263 | std::remove_extent_t* data_ = nullptr; 264 | }; 265 | 266 | struct make_unique_small_impl; 267 | 268 | } // namespace smp::detail 269 | 270 | namespace smp 271 | { 272 | template 273 | class small_unique_ptr : private detail::small_unique_ptr_base 274 | { 275 | public: 276 | static_assert(!std::is_bounded_array_v, "Bounded array types are not supported."); 277 | static_assert(Size >= sizeof(T*), "Size must be at least the size of a pointer."); 278 | static_assert(!(Size % sizeof(T*)), "Size must be a multiple of the size of a pointer."); 279 | 280 | using element_type = std::remove_extent_t; 281 | using pointer = std::remove_extent_t*; 282 | using reference = std::remove_extent_t&; 283 | 284 | struct constructor_tag_t {}; 285 | 286 | constexpr small_unique_ptr() noexcept = default; 287 | constexpr small_unique_ptr(std::nullptr_t) noexcept {} 288 | 289 | constexpr small_unique_ptr(small_unique_ptr&& other) noexcept : 290 | small_unique_ptr(std::move(other), constructor_tag_t{}) 291 | {} 292 | 293 | template 294 | constexpr small_unique_ptr(small_unique_ptr&& other) noexcept : 295 | small_unique_ptr(std::move(other), constructor_tag_t{}) 296 | {} 297 | 298 | template 299 | constexpr small_unique_ptr(small_unique_ptr&& other, constructor_tag_t) noexcept 300 | { 301 | static_assert(!detail::is_proper_base_of_v || std::has_virtual_destructor_v); 302 | 303 | if (!other.is_stack_allocated()) 304 | { 305 | this->data_ = std::exchange(other.data_, nullptr); 306 | return; 307 | } 308 | if constexpr (!detail::is_always_heap_allocated_v) // other.is_stack_allocated() 309 | { 310 | other.move_buffer_to(*this); 311 | this->data_ = std::launder(this->buffer(other.template offsetof_base())); 312 | other.reset(); 313 | } 314 | } 315 | 316 | constexpr small_unique_ptr& operator=(small_unique_ptr&& other) noexcept 317 | { 318 | if (std::addressof(other) == this) [[unlikely]] return *this; 319 | 320 | return operator=(std::move(other)); 321 | } 322 | 323 | template 324 | constexpr small_unique_ptr& operator=(small_unique_ptr&& other) noexcept 325 | { 326 | static_assert(!detail::is_proper_base_of_v || std::has_virtual_destructor_v); 327 | 328 | if (!other.is_stack_allocated()) 329 | { 330 | reset(std::exchange(other.data_, nullptr)); 331 | return *this; 332 | } 333 | if constexpr (!detail::is_always_heap_allocated_v) // other.is_stack_allocated() 334 | { 335 | reset(); 336 | other.move_buffer_to(*this); 337 | this->data_ = std::launder(this->buffer(other.template offsetof_base())); 338 | other.reset(); 339 | } 340 | return *this; 341 | } 342 | 343 | constexpr small_unique_ptr& operator=(std::nullptr_t) noexcept 344 | { 345 | reset(); 346 | return *this; 347 | } 348 | 349 | constexpr ~small_unique_ptr() noexcept 350 | { 351 | destroy(); 352 | } 353 | 354 | constexpr void reset(pointer new_data = pointer{}) noexcept 355 | { 356 | destroy(); 357 | this->data_ = new_data; 358 | if constexpr (requires { small_unique_ptr::move_; }) this->move_ = nullptr; 359 | } 360 | 361 | constexpr void swap(small_unique_ptr& other) noexcept 362 | { 363 | if constexpr (small_unique_ptr::is_always_heap_allocated()) 364 | { 365 | std::swap(this->data_, other.data_); 366 | } 367 | else if (!is_stack_allocated() && !other.is_stack_allocated()) 368 | { 369 | std::swap(this->data_, other.data_); 370 | } 371 | else if (is_stack_allocated() && other.is_stack_allocated()) 372 | { 373 | small_unique_ptr temp = std::move(other); 374 | other = std::move(*this); 375 | *this = std::move(temp); 376 | } 377 | else if (!is_stack_allocated() && other.is_stack_allocated()) 378 | { 379 | const pointer new_data = this->buffer(other.offsetof_base()); 380 | other.move_buffer_to(*this); 381 | other.reset(std::exchange(this->data_, std::launder(new_data))); 382 | } 383 | else /* if (is_stack_allocated() && !other.is_stack_allocated()) */ 384 | { 385 | const pointer new_data = other.buffer(this->offsetof_base()); 386 | this->move_buffer_to(other); 387 | this->reset(std::exchange(other.data_, std::launder(new_data))); 388 | } 389 | } 390 | 391 | [[nodiscard]] 392 | constexpr pointer release() noexcept = delete; 393 | 394 | [[nodiscard]] 395 | constexpr bool is_stack_allocated() const noexcept 396 | { 397 | return small_unique_ptr::small_unique_ptr_base::is_stack_allocated(); 398 | } 399 | 400 | [[nodiscard]] 401 | static constexpr bool is_always_heap_allocated() noexcept 402 | { 403 | return detail::is_always_heap_allocated_v; 404 | } 405 | 406 | [[nodiscard]] 407 | static constexpr std::size_t stack_buffer_size() noexcept 408 | { 409 | if constexpr (is_always_heap_allocated()) return 0; 410 | else return sizeof(typename small_unique_ptr::buffer_t); 411 | } 412 | 413 | [[nodiscard]] 414 | static constexpr std::size_t stack_array_size() noexcept requires(std::is_array_v) 415 | { 416 | return stack_buffer_size() / sizeof(std::remove_extent_t); 417 | } 418 | 419 | [[nodiscard]] 420 | constexpr pointer get() const noexcept 421 | { 422 | return this->data_; 423 | } 424 | 425 | [[nodiscard]] 426 | constexpr explicit operator bool() const noexcept 427 | { 428 | return static_cast(this->data_); 429 | } 430 | 431 | [[nodiscard]] 432 | constexpr reference operator*() const noexcept(detail::is_nothrow_dereferenceable_v) requires(!std::is_array_v) 433 | { 434 | assert(this->data_); 435 | return *this->data_; 436 | } 437 | 438 | [[nodiscard]] 439 | constexpr pointer operator->() const noexcept requires(!std::is_array_v) 440 | { 441 | assert(this->data_); 442 | return this->data_; 443 | } 444 | 445 | [[nodiscard]] 446 | constexpr reference operator[](std::size_t idx) const requires(std::is_array_v) 447 | { 448 | assert(this->data_); 449 | return this->data_[idx]; 450 | } 451 | 452 | constexpr bool operator==(std::nullptr_t) const noexcept 453 | { 454 | return this->data_ == pointer{ nullptr }; 455 | } 456 | 457 | constexpr std::strong_ordering operator<=>(std::nullptr_t) const noexcept 458 | { 459 | return std::compare_three_way{}(this->data_, pointer{ nullptr }); 460 | } 461 | 462 | template 463 | constexpr bool operator==(const small_unique_ptr& rhs) const noexcept 464 | { 465 | return this->data_ == rhs.data_; 466 | } 467 | 468 | template 469 | constexpr std::strong_ordering operator<=>(const small_unique_ptr& rhs) const noexcept 470 | { 471 | return std::compare_three_way{}(this->data_, rhs.data_); 472 | } 473 | 474 | template 475 | friend std::basic_ostream& operator<<(std::basic_ostream& os, const small_unique_ptr& p) 476 | { 477 | return os << p.get(); 478 | } 479 | 480 | constexpr friend void swap(small_unique_ptr& lhs, small_unique_ptr& rhs) noexcept 481 | { 482 | lhs.swap(rhs); 483 | } 484 | 485 | private: 486 | template 487 | constexpr std::ptrdiff_t offsetof_base() const noexcept requires(std::is_polymorphic_v) 488 | { 489 | if (!is_stack_allocated()) return 0; 490 | 491 | const auto derived_ptr = reinterpret_cast(this->buffer()); 492 | const auto base_ptr = reinterpret_cast(static_cast(this->data_)); 493 | 494 | return base_ptr - derived_ptr; 495 | } 496 | 497 | template 498 | constexpr std::ptrdiff_t offsetof_base() const noexcept requires(!std::is_polymorphic_v) 499 | { 500 | return 0; 501 | } 502 | 503 | constexpr void destroy() noexcept requires(!std::is_array_v) 504 | { 505 | is_stack_allocated() ? std::destroy_at(this->data_) : delete this->data_; 506 | } 507 | 508 | constexpr void destroy() noexcept requires(std::is_array_v) 509 | { 510 | is_stack_allocated() ? std::destroy(this->data_, this->data_ + stack_array_size()) : delete[] this->data_; 511 | } 512 | 513 | template 514 | friend class small_unique_ptr; 515 | 516 | friend struct detail::make_unique_small_impl; 517 | }; 518 | 519 | } // namespace smp 520 | 521 | namespace std 522 | { 523 | template 524 | struct hash> 525 | { 526 | std::size_t operator()(const smp::small_unique_ptr& p) const noexcept 527 | { 528 | return std::hash::pointer>{}(p.get()); 529 | } 530 | }; 531 | 532 | } // namespace std 533 | 534 | namespace smp::detail 535 | { 536 | struct make_unique_small_impl 537 | { 538 | template 539 | static constexpr small_unique_ptr invoke_scalar(Args&&... args) 540 | noexcept(std::is_nothrow_constructible_v && !detail::is_always_heap_allocated_v) 541 | { 542 | small_unique_ptr ptr; 543 | 544 | if (detail::is_always_heap_allocated_v || std::is_constant_evaluated()) 545 | { 546 | ptr.data_ = new T(std::forward(args)...); 547 | } 548 | else if constexpr (!detail::is_always_heap_allocated_v && std::is_polymorphic_v && !detail::has_virtual_move) 549 | { 550 | ptr.data_ = std::construct_at(ptr.buffer(), std::forward(args)...); 551 | ptr.move_ = detail::move_buffer>; 552 | } 553 | else if constexpr (!detail::is_always_heap_allocated_v) 554 | { 555 | ptr.data_ = std::construct_at(ptr.buffer(), std::forward(args)...); 556 | } 557 | 558 | return ptr; 559 | } 560 | 561 | template 562 | static constexpr small_unique_ptr invoke_array(std::size_t count) 563 | { 564 | small_unique_ptr ptr; 565 | 566 | if (detail::is_always_heap_allocated_v || (ptr.stack_array_size() < count) || std::is_constant_evaluated()) 567 | { 568 | ptr.data_ = new std::remove_extent_t[count]{}; 569 | } 570 | else if constexpr (!detail::is_always_heap_allocated_v) 571 | { 572 | std::uninitialized_value_construct_n(ptr.buffer(), ptr.stack_array_size()); 573 | ptr.data_ = std::launder(ptr.buffer()); 574 | } 575 | 576 | return ptr; 577 | } 578 | 579 | template 580 | static constexpr small_unique_ptr invoke_for_overwrite_scalar() 581 | noexcept(std::is_nothrow_default_constructible_v && !detail::is_always_heap_allocated_v) 582 | { 583 | small_unique_ptr ptr; 584 | 585 | if (detail::is_always_heap_allocated_v || std::is_constant_evaluated()) 586 | { 587 | ptr.data_ = new T; 588 | } 589 | else if constexpr (!detail::is_always_heap_allocated_v && std::is_polymorphic_v && !detail::has_virtual_move) 590 | { 591 | ptr.data_ = ::new(static_cast(ptr.buffer())) std::remove_cv_t; 592 | ptr.move_ = detail::move_buffer>; 593 | } 594 | else if constexpr (!detail::is_always_heap_allocated_v) 595 | { 596 | ptr.data_ = ::new(static_cast(ptr.buffer())) std::remove_cv_t; 597 | } 598 | 599 | return ptr; 600 | } 601 | 602 | template 603 | static constexpr small_unique_ptr invoke_for_overwrite_array(std::size_t count) 604 | { 605 | small_unique_ptr ptr; 606 | 607 | if (detail::is_always_heap_allocated_v || (ptr.stack_array_size() < count) || std::is_constant_evaluated()) 608 | { 609 | ptr.data_ = new std::remove_extent_t[count]; 610 | } 611 | else if constexpr (!detail::is_always_heap_allocated_v) 612 | { 613 | std::uninitialized_default_construct_n(ptr.buffer(), ptr.stack_array_size()); 614 | ptr.data_ = std::launder(ptr.buffer()); 615 | } 616 | 617 | return ptr; 618 | } 619 | }; 620 | 621 | } // namespace smp::detail 622 | 623 | namespace smp 624 | { 625 | template 626 | [[nodiscard]] constexpr small_unique_ptr make_unique_small(Args&&... args) 627 | noexcept(std::is_nothrow_constructible_v && !detail::is_always_heap_allocated_v) requires(!std::is_array_v) 628 | { 629 | return detail::make_unique_small_impl::invoke_scalar(std::forward(args)...); 630 | } 631 | 632 | template 633 | [[nodiscard]] constexpr small_unique_ptr make_unique_small(std::size_t count) requires(std::is_unbounded_array_v) 634 | { 635 | return detail::make_unique_small_impl::invoke_array(count); 636 | } 637 | 638 | template 639 | [[nodiscard]] constexpr small_unique_ptr make_unique_small(Args&&...) requires(std::is_bounded_array_v) = delete; 640 | 641 | 642 | template 643 | [[nodiscard]] constexpr small_unique_ptr make_unique_small_for_overwrite() 644 | noexcept(std::is_nothrow_default_constructible_v && !detail::is_always_heap_allocated_v) requires(!std::is_array_v) 645 | { 646 | return detail::make_unique_small_impl::invoke_for_overwrite_scalar(); 647 | } 648 | 649 | template 650 | [[nodiscard]] constexpr small_unique_ptr make_unique_small_for_overwrite(std::size_t count) requires(std::is_unbounded_array_v) 651 | { 652 | return detail::make_unique_small_impl::invoke_for_overwrite_array(count); 653 | } 654 | 655 | template 656 | [[nodiscard]] constexpr small_unique_ptr make_unique_small_for_overwrite(Args&&...) requires(std::is_bounded_array_v) = delete; 657 | 658 | } // namespace smp 659 | 660 | #endif // !SMALL_UNIQUE_PTR_HPP 661 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Catch2 3 REQUIRED) 2 | 3 | include(Catch) 4 | 5 | add_executable(small_unique_ptr_test "${CMAKE_CURRENT_SOURCE_DIR}/small_unique_ptr.cpp") 6 | target_link_libraries(small_unique_ptr_test PRIVATE Catch2::Catch2WithMain small_unique_ptr) 7 | 8 | catch_discover_tests(small_unique_ptr_test) -------------------------------------------------------------------------------- /test/small_unique_ptr.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2024 Krisztián Rugási. Subject to the MIT License. */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace smp; 12 | 13 | struct SmallPOD { char dummy_; }; 14 | struct LargePOD { char dummy_[128]; }; 15 | 16 | struct Base 17 | { 18 | constexpr virtual int value() const { return 0; } 19 | constexpr virtual int padding() const { return 0; } 20 | constexpr virtual ~Base() noexcept {}; 21 | }; 22 | 23 | template 24 | struct Derived : Base 25 | { 26 | public: 27 | constexpr Derived() = default; 28 | constexpr Derived(int n) noexcept : value_(n) {} 29 | constexpr ~Derived() noexcept override {} 30 | constexpr int value() const override { return value_; } 31 | constexpr int padding() const override { return Padding; } 32 | 33 | private: 34 | unsigned char padding_[Padding] = {}; 35 | int value_ = Padding; 36 | }; 37 | 38 | using SmallDerived = Derived<32>; 39 | using LargeDerived = Derived<64>; 40 | 41 | struct BaseIntrusive 42 | { 43 | constexpr virtual int value() const { return 0; } 44 | constexpr virtual int padding() const { return 0; } 45 | constexpr virtual ~BaseIntrusive() noexcept {}; 46 | 47 | virtual void small_unique_ptr_move(void* dst) noexcept 48 | { 49 | std::construct_at(static_cast(dst), std::move(*this)); 50 | } 51 | }; 52 | 53 | template 54 | struct DerivedIntrusive : BaseIntrusive 55 | { 56 | public: 57 | constexpr DerivedIntrusive() = default; 58 | constexpr DerivedIntrusive(int n) noexcept : value_(n) {} 59 | constexpr ~DerivedIntrusive() noexcept override {} 60 | constexpr int value() const override { return value_; } 61 | constexpr int padding() const override { return Padding; } 62 | 63 | void small_unique_ptr_move(void* dst) noexcept override 64 | { 65 | std::construct_at(static_cast(dst), std::move(*this)); 66 | } 67 | 68 | private: 69 | unsigned char padding_[Padding] = {}; 70 | int value_ = Padding; 71 | }; 72 | 73 | using SmallIntrusive = DerivedIntrusive<32>; 74 | using LargeIntrusive = DerivedIntrusive<64>; 75 | 76 | 77 | TEST_CASE("object_layout", "[small_unique_ptr]") 78 | { 79 | STATIC_REQUIRE(std::is_standard_layout_v>); 80 | STATIC_REQUIRE(std::is_standard_layout_v>); 81 | 82 | STATIC_REQUIRE(std::is_standard_layout_v>); 83 | STATIC_REQUIRE(std::is_standard_layout_v>); 84 | 85 | STATIC_REQUIRE(std::is_standard_layout_v>); 86 | STATIC_REQUIRE(std::is_standard_layout_v>); 87 | STATIC_REQUIRE(std::is_standard_layout_v>); 88 | 89 | STATIC_REQUIRE(std::is_standard_layout_v>); 90 | STATIC_REQUIRE(std::is_standard_layout_v>); 91 | STATIC_REQUIRE(std::is_standard_layout_v>); 92 | } 93 | 94 | TEST_CASE("object_size_default", "[small_unique_ptr]") 95 | { 96 | STATIC_REQUIRE(sizeof(small_unique_ptr) <= 2 * sizeof(void*)); 97 | STATIC_REQUIRE(sizeof(small_unique_ptr) == sizeof(void*)); 98 | 99 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(void*)); 100 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(void*)); 101 | 102 | 103 | STATIC_REQUIRE(sizeof(small_unique_ptr) == detail::default_small_ptr_size); 104 | STATIC_REQUIRE(sizeof(small_unique_ptr) == sizeof(void*)); 105 | 106 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(void*)); 107 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(void*)); 108 | 109 | 110 | STATIC_REQUIRE(sizeof(small_unique_ptr) == detail::default_small_ptr_size); 111 | STATIC_REQUIRE(sizeof(small_unique_ptr) == sizeof(void*)); 112 | 113 | STATIC_REQUIRE(alignof(small_unique_ptr) == detail::default_small_ptr_size); 114 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(void*)); 115 | 116 | 117 | STATIC_REQUIRE(sizeof(small_unique_ptr) == detail::default_small_ptr_size); 118 | STATIC_REQUIRE(sizeof(small_unique_ptr) == sizeof(void*)); 119 | 120 | STATIC_REQUIRE(alignof(small_unique_ptr) == detail::default_small_ptr_size); 121 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(void*)); 122 | } 123 | 124 | TEMPLATE_TEST_CASE("object_size_custom", "[small_unique_ptr]", Base, BaseIntrusive) 125 | { 126 | STATIC_REQUIRE(sizeof(small_unique_ptr) == 8); 127 | STATIC_REQUIRE(sizeof(small_unique_ptr) <= 16); // Base will be always heap allocated on 64 bit arch 128 | STATIC_REQUIRE(sizeof(small_unique_ptr) == 24); 129 | STATIC_REQUIRE(sizeof(small_unique_ptr) == 32); 130 | STATIC_REQUIRE(sizeof(small_unique_ptr) == 40); 131 | STATIC_REQUIRE(sizeof(small_unique_ptr) == 48); 132 | STATIC_REQUIRE(sizeof(small_unique_ptr) == 56); 133 | STATIC_REQUIRE(sizeof(small_unique_ptr) == 64); 134 | STATIC_REQUIRE(sizeof(small_unique_ptr) == 128); 135 | } 136 | 137 | TEMPLATE_TEST_CASE("object_align_custom", "[small_unique_ptr]", Base, BaseIntrusive) 138 | { 139 | STATIC_REQUIRE(alignof(small_unique_ptr) == 8); 140 | STATIC_REQUIRE(alignof(small_unique_ptr) <= 16); // Base will be always heap allocated on 64 bit arch 141 | STATIC_REQUIRE(alignof(small_unique_ptr) == 8); 142 | STATIC_REQUIRE(alignof(small_unique_ptr) == 32); 143 | STATIC_REQUIRE(alignof(small_unique_ptr) == 8); 144 | STATIC_REQUIRE(alignof(small_unique_ptr) == 16); 145 | STATIC_REQUIRE(alignof(small_unique_ptr) == 8); 146 | STATIC_REQUIRE(alignof(small_unique_ptr) == 64); 147 | STATIC_REQUIRE(alignof(small_unique_ptr) == 128); 148 | } 149 | 150 | TEST_CASE("object_align_custom_pod", "[small_unique_ptr]") 151 | { 152 | static_assert(alignof(SmallPOD) <= alignof(SmallPOD*)); 153 | 154 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 155 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 156 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 157 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 158 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 159 | 160 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 161 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 162 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 163 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 164 | STATIC_REQUIRE(alignof(small_unique_ptr) == alignof(SmallPOD*)); 165 | } 166 | 167 | TEST_CASE("stack_buffer_size", "[small_unique_ptr]") 168 | { 169 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() == sizeof(SmallPOD)); 170 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() == 0); 171 | 172 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() != 0); 173 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() == 0); 174 | 175 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() != 0); 176 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() != 0); 177 | 178 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() == 0); 179 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() == 0); 180 | 181 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() > small_unique_ptr::stack_buffer_size()); 182 | STATIC_REQUIRE(small_unique_ptr::stack_buffer_size() > small_unique_ptr::stack_buffer_size()); 183 | 184 | CHECKED_IF(sizeof(void*) == 8) 185 | { 186 | REQUIRE(small_unique_ptr::stack_buffer_size() == 48); 187 | REQUIRE(small_unique_ptr::stack_buffer_size() == 56); 188 | 189 | REQUIRE(small_unique_ptr::stack_buffer_size() == 48); 190 | REQUIRE(small_unique_ptr::stack_buffer_size() == 56); 191 | 192 | REQUIRE(small_unique_ptr::stack_buffer_size() == 56); 193 | 194 | REQUIRE(small_unique_ptr::stack_array_size() == 56); 195 | REQUIRE(small_unique_ptr::stack_array_size() == 0); 196 | 197 | REQUIRE(small_unique_ptr::stack_array_size() > 0); 198 | } 199 | } 200 | 201 | TEMPLATE_TEST_CASE("construct_scalar", "[small_unique_ptr]", SmallPOD, LargePOD, Base, SmallDerived, LargeDerived, BaseIntrusive, SmallIntrusive, LargeIntrusive) 202 | { 203 | STATIC_REQUIRE( std::invoke([]{ (void) small_unique_ptr(); return true; }) ); 204 | STATIC_REQUIRE( std::invoke([]{ (void) small_unique_ptr(); return true; }) ); 205 | 206 | STATIC_REQUIRE( std::invoke([]{ (void) small_unique_ptr(nullptr); return true; }) ); 207 | STATIC_REQUIRE( std::invoke([]{ (void) small_unique_ptr(nullptr); return true; }) ); 208 | } 209 | 210 | TEMPLATE_TEST_CASE("make_unique_scalar", "[small_unique_ptr]", SmallPOD, LargePOD, Base, SmallDerived, LargeDerived, BaseIntrusive, SmallIntrusive, LargeIntrusive) 211 | { 212 | STATIC_REQUIRE( std::invoke([]{ (void) make_unique_small(); return true; }) ); 213 | STATIC_REQUIRE( std::invoke([]{ (void) make_unique_small(); return true; }) ); 214 | 215 | (void) make_unique_small(); 216 | (void) make_unique_small(); 217 | 218 | SUCCEED(); 219 | } 220 | 221 | TEMPLATE_TEST_CASE("make_unique_for_overwrite_scalar", "[small_unique_ptr]", SmallPOD, LargePOD, Base, SmallDerived, LargeDerived, BaseIntrusive, SmallIntrusive, LargeIntrusive) 222 | { 223 | STATIC_REQUIRE( std::invoke([]{ (void) make_unique_small_for_overwrite(); return true; }) ); 224 | 225 | (void) make_unique_small_for_overwrite(); 226 | 227 | SUCCEED(); 228 | } 229 | 230 | TEMPLATE_TEST_CASE("construction_array", "[small_unique_ptr]", SmallPOD, LargePOD) 231 | { 232 | STATIC_REQUIRE( std::invoke([]{ (void) small_unique_ptr(); return true; }) ); 233 | STATIC_REQUIRE( std::invoke([]{ (void) small_unique_ptr(); return true; }) ); 234 | 235 | STATIC_REQUIRE( std::invoke([]{ (void) small_unique_ptr(nullptr); return true; }) ); 236 | STATIC_REQUIRE( std::invoke([]{ (void) small_unique_ptr(nullptr); return true; }) ); 237 | } 238 | 239 | TEMPLATE_TEST_CASE("make_unique_array", "[small_unique_ptr]", SmallPOD, LargePOD) 240 | { 241 | STATIC_REQUIRE( std::invoke([]{ (void) make_unique_small(2); return true; }) ); 242 | STATIC_REQUIRE( std::invoke([]{ (void) make_unique_small(2); return true; }) ); 243 | 244 | (void) make_unique_small(2); 245 | (void) make_unique_small(2); 246 | 247 | (void) make_unique_small(0); 248 | 249 | SUCCEED(); 250 | } 251 | 252 | TEST_CASE("make_unique_for_overwrite_array", "[small_unique_ptr]") 253 | { 254 | STATIC_REQUIRE( std::invoke([]{ (void) make_unique_small_for_overwrite(2); return true; }) ); 255 | STATIC_REQUIRE( std::invoke([]{ (void) make_unique_small_for_overwrite(2); return true; }) ); 256 | 257 | (void) make_unique_small_for_overwrite(2); 258 | (void) make_unique_small_for_overwrite(2); 259 | 260 | (void) make_unique_small_for_overwrite(0); 261 | (void) make_unique_small_for_overwrite(0); 262 | 263 | SUCCEED(); 264 | } 265 | 266 | TEST_CASE("noexcept_construction", "[small_unique_ptr]") 267 | { 268 | STATIC_REQUIRE(noexcept(make_unique_small())); 269 | STATIC_REQUIRE(!noexcept(make_unique_small())); 270 | 271 | STATIC_REQUIRE(noexcept(make_unique_small_for_overwrite())); 272 | STATIC_REQUIRE(!noexcept(make_unique_small_for_overwrite())); 273 | } 274 | 275 | TEST_CASE("is_always_heap_allocated", "[small_unique_ptr]") 276 | { 277 | STATIC_REQUIRE(!small_unique_ptr::is_always_heap_allocated()); 278 | STATIC_REQUIRE(small_unique_ptr::is_always_heap_allocated()); 279 | 280 | STATIC_REQUIRE(!small_unique_ptr::is_always_heap_allocated()); 281 | STATIC_REQUIRE(small_unique_ptr::is_always_heap_allocated()); 282 | 283 | STATIC_REQUIRE(!small_unique_ptr::is_always_heap_allocated()); 284 | STATIC_REQUIRE(small_unique_ptr::is_always_heap_allocated()); 285 | 286 | STATIC_REQUIRE(!small_unique_ptr::is_always_heap_allocated()); 287 | STATIC_REQUIRE(small_unique_ptr::is_always_heap_allocated()); 288 | } 289 | 290 | TEST_CASE("is_stack_allocated", "[small_unique_ptr]") 291 | { 292 | STATIC_REQUIRE( !std::invoke([]{ return make_unique_small().is_stack_allocated(); }) ); 293 | STATIC_REQUIRE( !std::invoke([]{ return make_unique_small().is_stack_allocated(); }) ); 294 | 295 | STATIC_REQUIRE( !std::invoke([]{ return make_unique_small().is_stack_allocated(); }) ); 296 | STATIC_REQUIRE( !std::invoke([]{ return make_unique_small().is_stack_allocated(); }) ); 297 | 298 | STATIC_REQUIRE( !std::invoke([]{ return make_unique_small().is_stack_allocated(); }) ); 299 | STATIC_REQUIRE( !std::invoke([]{ return make_unique_small().is_stack_allocated(); }) ); 300 | 301 | STATIC_REQUIRE( !std::invoke([] { return make_unique_small(2).is_stack_allocated(); }) ); 302 | STATIC_REQUIRE( !std::invoke([] { return make_unique_small(2).is_stack_allocated(); }) ); 303 | 304 | small_unique_ptr p1 = make_unique_small(); 305 | small_unique_ptr p2 = make_unique_small(); 306 | REQUIRE(p1.is_stack_allocated()); 307 | REQUIRE(!p2.is_stack_allocated()); 308 | 309 | small_unique_ptr p3 = make_unique_small(); 310 | small_unique_ptr p4 = make_unique_small(); 311 | REQUIRE(p3.is_stack_allocated()); 312 | REQUIRE(!p4.is_stack_allocated()); 313 | 314 | small_unique_ptr p5 = make_unique_small(); 315 | small_unique_ptr p6 = make_unique_small(); 316 | REQUIRE(p5.is_stack_allocated()); 317 | REQUIRE(!p6.is_stack_allocated()); 318 | 319 | small_unique_ptr p7 = make_unique_small(3); 320 | small_unique_ptr p8 = make_unique_small(1); 321 | REQUIRE(p7.is_stack_allocated()); 322 | REQUIRE(!p8.is_stack_allocated()); 323 | 324 | small_unique_ptr np(nullptr); 325 | REQUIRE(!np.is_stack_allocated()); 326 | } 327 | 328 | TEST_CASE("comparisons", "[small_unique_ptr]") 329 | { 330 | STATIC_REQUIRE(small_unique_ptr(nullptr) == nullptr); 331 | STATIC_REQUIRE(small_unique_ptr(nullptr) == nullptr); 332 | 333 | STATIC_REQUIRE(make_unique_small() != nullptr); 334 | STATIC_REQUIRE(make_unique_small() != nullptr); 335 | 336 | STATIC_REQUIRE(make_unique_small() != make_unique_small()); 337 | STATIC_REQUIRE(make_unique_small() != make_unique_small()); 338 | } 339 | 340 | TEMPLATE_TEST_CASE("bool_conversion", "[small_unique_ptr]", Base, SmallDerived, LargeDerived, SmallPOD, LargePOD) 341 | { 342 | STATIC_REQUIRE(!small_unique_ptr()); 343 | STATIC_REQUIRE(make_unique_small()); 344 | 345 | REQUIRE(!small_unique_ptr()); 346 | REQUIRE(make_unique_small()); 347 | } 348 | 349 | TEMPLATE_TEST_CASE("get", "[small_unique_ptr]", Base, SmallDerived, LargeDerived, SmallPOD, LargePOD) 350 | { 351 | STATIC_REQUIRE(small_unique_ptr().get() == nullptr); 352 | STATIC_REQUIRE(make_unique_small().get() != nullptr); 353 | 354 | REQUIRE(small_unique_ptr() == nullptr); 355 | REQUIRE(make_unique_small().get() != nullptr); 356 | } 357 | 358 | TEST_CASE("dereference", "[small_unique_ptr]") 359 | { 360 | STATIC_REQUIRE((*make_unique_small()).padding() == 32); 361 | STATIC_REQUIRE((*make_unique_small()).padding() == 64); 362 | 363 | STATIC_REQUIRE(make_unique_small()->padding() == 32); 364 | STATIC_REQUIRE(make_unique_small()->padding() == 64); 365 | 366 | const auto p0 = make_unique_small(); 367 | const auto p1 = make_unique_small(); 368 | const auto p2 = make_unique_small(); 369 | 370 | REQUIRE((*p0).value() == 0); 371 | REQUIRE((*p1).value() == 32); 372 | REQUIRE((*p2).value() == 64); 373 | 374 | REQUIRE(p0->value() == 0); 375 | REQUIRE(p1->value() == 32); 376 | REQUIRE(p2->value() == 64); 377 | } 378 | 379 | TEST_CASE("move_construct_plain", "[small_unique_ptr]") 380 | { 381 | STATIC_REQUIRE(32 == std::invoke([] { small_unique_ptr p = make_unique_small(); return p->padding(); })); 382 | STATIC_REQUIRE(64 == std::invoke([] { small_unique_ptr p = make_unique_small(); return p->padding(); })); 383 | 384 | STATIC_REQUIRE(32 == std::invoke([] { small_unique_ptr p = make_unique_small(); return p->padding(); })); 385 | STATIC_REQUIRE(64 == std::invoke([] { small_unique_ptr p = make_unique_small(); return p->padding(); })); 386 | 387 | STATIC_REQUIRE( std::invoke([] { small_unique_ptr p = make_unique_small(); return true; }) ); 388 | STATIC_REQUIRE( std::invoke([] { small_unique_ptr p = make_unique_small(); return true; }) ); 389 | 390 | 391 | small_unique_ptr base1 = make_unique_small(); 392 | small_unique_ptr base2 = make_unique_small(); 393 | REQUIRE(base1->value() == 32); 394 | REQUIRE(base2->value() == 64); 395 | 396 | small_unique_ptr cbase = std::move(base1); 397 | REQUIRE(cbase->value() == 32); 398 | REQUIRE(base1 == nullptr); 399 | 400 | 401 | small_unique_ptr ibase1 = make_unique_small(); 402 | small_unique_ptr ibase2 = make_unique_small(); 403 | REQUIRE(ibase1->value() == 32); 404 | REQUIRE(ibase2->value() == 64); 405 | 406 | small_unique_ptr icbase = std::move(ibase1); 407 | REQUIRE(icbase->value() == 32); 408 | REQUIRE(ibase1 == nullptr); 409 | 410 | small_unique_ptr cpod1 = make_unique_small(); 411 | small_unique_ptr cpod2 = make_unique_small(); 412 | SUCCEED(); 413 | } 414 | 415 | TEST_CASE("move_construct_array", "[small_unique_ptr]") 416 | { 417 | STATIC_REQUIRE( std::invoke([] { small_unique_ptr p = make_unique_small(4); return true; }) ); 418 | STATIC_REQUIRE( std::invoke([] { small_unique_ptr p = make_unique_small(2); return true; }) ); 419 | 420 | small_unique_ptr cpod1 = make_unique_small(4); 421 | small_unique_ptr cpod2 = make_unique_small(2); 422 | 423 | small_unique_ptr cpod3 = make_unique_small(0); 424 | small_unique_ptr cpod4 = make_unique_small(0); 425 | SUCCEED(); 426 | } 427 | 428 | TEST_CASE("move_assignment_plain", "[small_unique_ptr]") 429 | { 430 | STATIC_REQUIRE(32 == std::invoke([] { small_unique_ptr p; p = make_unique_small(); return p->padding(); })); 431 | STATIC_REQUIRE(64 == std::invoke([] { small_unique_ptr p; p = make_unique_small(); return p->padding(); })); 432 | 433 | STATIC_REQUIRE(32 == std::invoke([] { small_unique_ptr p; p = make_unique_small(); return p->padding(); })); 434 | STATIC_REQUIRE(64 == std::invoke([] { small_unique_ptr p; p = make_unique_small(); return p->padding(); })); 435 | 436 | STATIC_REQUIRE( std::invoke([] { small_unique_ptr p; p = make_unique_small(); return true; }) ); 437 | STATIC_REQUIRE( std::invoke([] { small_unique_ptr p; p = make_unique_small(); return true; }) ); 438 | 439 | 440 | small_unique_ptr base; 441 | 442 | base = make_unique_small(); 443 | REQUIRE(base->value() == 32); 444 | 445 | base = make_unique_small(); 446 | REQUIRE(base->value() == 64); 447 | 448 | base = nullptr; 449 | REQUIRE(!base); 450 | 451 | 452 | small_unique_ptr ibase; 453 | 454 | ibase = make_unique_small(); 455 | REQUIRE(ibase->value() == 32); 456 | 457 | ibase = make_unique_small(); 458 | REQUIRE(ibase->value() == 64); 459 | 460 | ibase = nullptr; 461 | REQUIRE(!ibase); 462 | 463 | 464 | small_unique_ptr cpod1; cpod1 = make_unique_small(); 465 | small_unique_ptr cpod2; cpod2 = make_unique_small(); 466 | SUCCEED(); 467 | } 468 | 469 | TEST_CASE("move_assignment_array", "[small_unique_ptr]") 470 | { 471 | STATIC_REQUIRE( std::invoke([] { small_unique_ptr p; p = make_unique_small(4); return true; }) ); 472 | STATIC_REQUIRE( std::invoke([] { small_unique_ptr p; p = make_unique_small(4); return true; }) ); 473 | 474 | small_unique_ptr cpod1; cpod1 = make_unique_small(4); 475 | small_unique_ptr cpod2; cpod2 = make_unique_small(4); 476 | SUCCEED(); 477 | } 478 | 479 | TEST_CASE("swap_pod", "[small_unique_ptr]") 480 | { 481 | small_unique_ptr p1 = nullptr; 482 | small_unique_ptr p2 = make_unique_small(); 483 | 484 | using std::swap; 485 | swap(p1, p2); 486 | 487 | REQUIRE(p2 == nullptr); 488 | REQUIRE(p1 != nullptr); 489 | } 490 | 491 | TEST_CASE("swap_array", "[small_unique_ptr]") 492 | { 493 | small_unique_ptr p1 = nullptr; 494 | small_unique_ptr p2 = make_unique_small(3); 495 | 496 | using std::swap; 497 | swap(p1, p2); 498 | 499 | REQUIRE(p2 == nullptr); 500 | REQUIRE(p1[2].value() == 32); 501 | 502 | swap(p1, p2); 503 | 504 | REQUIRE(p1 == nullptr); 505 | REQUIRE(p2[1].value() == 32); 506 | } 507 | 508 | TEST_CASE("swap_large", "[small_unique_ptr]") 509 | { 510 | small_unique_ptr p1 = nullptr; 511 | small_unique_ptr p2 = make_unique_small(); 512 | 513 | using std::swap; 514 | swap(p1, p2); 515 | 516 | REQUIRE(p2 == nullptr); 517 | REQUIRE(p1->value() == 64); 518 | } 519 | 520 | TEST_CASE("swap_small", "[small_unique_ptr]") 521 | { 522 | small_unique_ptr p1 = make_unique_small(1); 523 | small_unique_ptr p2 = make_unique_small(2); 524 | 525 | using std::swap; 526 | swap(p1, p2); 527 | 528 | REQUIRE(p1->value() == 2); 529 | REQUIRE(p2->value() == 1); 530 | 531 | swap(p1, p2); 532 | 533 | REQUIRE(p1->value() == 1); 534 | REQUIRE(p2->value() == 2); 535 | } 536 | 537 | TEST_CASE("swap_mixed", "[small_unique_ptr]") 538 | { 539 | using std::swap; 540 | 541 | small_unique_ptr p1 = make_unique_small(); 542 | small_unique_ptr p2 = make_unique_small(); 543 | 544 | REQUIRE(p1->value() == 32); 545 | REQUIRE(p2->value() == 64); 546 | 547 | swap(p1, p2); 548 | 549 | REQUIRE(p1->value() == 64); 550 | REQUIRE(p2->value() == 32); 551 | 552 | swap(p1, p2); 553 | 554 | REQUIRE(p1->value() == 32); 555 | REQUIRE(p2->value() == 64); 556 | } 557 | 558 | TEST_CASE("swap_large_intrusive", "[small_unique_ptr]") 559 | { 560 | small_unique_ptr p1 = nullptr; 561 | small_unique_ptr p2 = make_unique_small(); 562 | 563 | using std::swap; 564 | swap(p1, p2); 565 | 566 | REQUIRE(p2 == nullptr); 567 | REQUIRE(p1->value() == 64); 568 | } 569 | 570 | TEST_CASE("swap_small_intrusive", "[small_unique_ptr]") 571 | { 572 | small_unique_ptr p1 = make_unique_small(1); 573 | small_unique_ptr p2 = make_unique_small(2); 574 | 575 | using std::swap; 576 | swap(p1, p2); 577 | 578 | REQUIRE(p1->value() == 2); 579 | REQUIRE(p2->value() == 1); 580 | 581 | swap(p1, p2); 582 | 583 | REQUIRE(p1->value() == 1); 584 | REQUIRE(p2->value() == 2); 585 | } 586 | 587 | TEST_CASE("swap_mixed_intrusive", "[small_unique_ptr]") 588 | { 589 | using std::swap; 590 | 591 | small_unique_ptr p1 = make_unique_small(); 592 | small_unique_ptr p2 = make_unique_small(); 593 | 594 | REQUIRE(p1->value() == 32); 595 | REQUIRE(p2->value() == 64); 596 | 597 | swap(p1, p2); 598 | 599 | REQUIRE(p1->value() == 64); 600 | REQUIRE(p2->value() == 32); 601 | 602 | swap(p1, p2); 603 | 604 | REQUIRE(p1->value() == 32); 605 | REQUIRE(p2->value() == 64); 606 | } 607 | 608 | TEST_CASE("constexpr_swap", "[small_unique_ptr]") 609 | { 610 | using std::swap; 611 | 612 | STATIC_REQUIRE(std::invoke([] 613 | { 614 | small_unique_ptr p1 = make_unique_small(); 615 | small_unique_ptr p2 = make_unique_small(); 616 | 617 | swap(p1, p2); 618 | 619 | return true; 620 | })); 621 | 622 | 623 | STATIC_REQUIRE(32 == std::invoke([] 624 | { 625 | small_unique_ptr p1 = make_unique_small(); 626 | small_unique_ptr p2 = make_unique_small(); 627 | 628 | swap(p1, p2); 629 | 630 | return p2->padding(); 631 | })); 632 | 633 | STATIC_REQUIRE(32 == std::invoke([] 634 | { 635 | small_unique_ptr p1 = make_unique_small(); 636 | small_unique_ptr p2 = make_unique_small(); 637 | 638 | swap(p1, p2); 639 | 640 | return p2->padding(); 641 | })); 642 | 643 | STATIC_REQUIRE(64 == std::invoke([] 644 | { 645 | small_unique_ptr p1 = make_unique_small(); 646 | small_unique_ptr p2 = make_unique_small(); 647 | 648 | swap(p1, p2); 649 | 650 | return p2->padding(); 651 | })); 652 | 653 | 654 | STATIC_REQUIRE(32 == std::invoke([] 655 | { 656 | small_unique_ptr p1 = make_unique_small(); 657 | small_unique_ptr p2 = make_unique_small(); 658 | 659 | swap(p1, p2); 660 | 661 | return p2->padding(); 662 | })); 663 | 664 | STATIC_REQUIRE(32 == std::invoke([] 665 | { 666 | small_unique_ptr p1 = make_unique_small(); 667 | small_unique_ptr p2 = make_unique_small(); 668 | 669 | swap(p1, p2); 670 | 671 | return p2->padding(); 672 | })); 673 | 674 | STATIC_REQUIRE(64 == std::invoke([] 675 | { 676 | small_unique_ptr p1 = make_unique_small(); 677 | small_unique_ptr p2 = make_unique_small(); 678 | 679 | swap(p1, p2); 680 | 681 | return p2->padding(); 682 | })); 683 | 684 | STATIC_REQUIRE(32 == std::invoke([] 685 | { 686 | small_unique_ptr p1 = make_unique_small(2); 687 | small_unique_ptr p2 = make_unique_small(4); 688 | 689 | swap(p1, p2); 690 | 691 | return p1[3].padding(); 692 | })); 693 | } 694 | 695 | struct A { virtual ~A() = default; }; 696 | struct C { virtual ~C() = default; }; 697 | struct B { char b = 2; virtual char value() const { return b; } virtual ~B() = default; }; 698 | struct D : A, B { char d = 5; char value() const override { return d; } }; 699 | struct E : C, D { char e = 6; char value() const override { return e; } }; 700 | 701 | TEST_CASE("move_construct_multiple_inheritance", "[small_unique_ptr]") 702 | { 703 | small_unique_ptr derived = make_unique_small(); 704 | REQUIRE(derived->b == 2); 705 | REQUIRE(derived->value() == 6); 706 | 707 | small_unique_ptr base = std::move(derived); 708 | REQUIRE(base->b == 2); 709 | REQUIRE(base->value() == 6); 710 | 711 | small_unique_ptr cbase = std::move(base); 712 | REQUIRE(cbase->b == 2); 713 | REQUIRE(cbase->value() == 6); 714 | } 715 | 716 | TEST_CASE("move_assignment_multiple_inheritance", "[small_unique_ptr]") 717 | { 718 | small_unique_ptr p1; 719 | 720 | p1 = make_unique_small(); 721 | REQUIRE(p1->value() == 2); 722 | 723 | p1 = make_unique_small(); 724 | REQUIRE(p1->value() == 5); 725 | 726 | p1 = make_unique_small(); 727 | REQUIRE(p1->value() == 2); 728 | 729 | p1 = make_unique_small(); 730 | REQUIRE(p1->value() == 6); 731 | } 732 | 733 | TEST_CASE("swap_multiple_inheritance", "[small_unique_ptr]") 734 | { 735 | using std::swap; 736 | 737 | small_unique_ptr p1 = make_unique_small(); 738 | small_unique_ptr p2 = make_unique_small(); 739 | 740 | REQUIRE(p1->value() == 5); 741 | REQUIRE(p2->value() == 6); 742 | 743 | swap(p1, p2); 744 | 745 | REQUIRE(p1->value() == 6); 746 | REQUIRE(p2->value() == 5); 747 | 748 | swap(p1, p2); 749 | 750 | REQUIRE(p1->value() == 5); 751 | REQUIRE(p2->value() == 6); 752 | 753 | p1 = nullptr; 754 | swap(p1, p2); 755 | 756 | REQUIRE(p1->value() == 6); 757 | REQUIRE(p2 == nullptr); 758 | 759 | swap(p1, p2); 760 | 761 | REQUIRE(p1 == nullptr); 762 | REQUIRE(p2->value() == 6); 763 | 764 | swap(p1, p2); 765 | 766 | REQUIRE(p1->value() == 6); 767 | REQUIRE(p2 == nullptr); 768 | } 769 | 770 | TEST_CASE("virtual_inheritance", "[small_unique_ptr]") 771 | { 772 | struct VBase { char a = 1; virtual char value() const { return a; } virtual ~VBase() = default; }; 773 | struct VMiddle1 : virtual VBase { char value() const override { return 2; } }; 774 | struct VMiddle2 : virtual VBase { char value() const override { return 3; } }; 775 | struct VDerived : VMiddle1, VMiddle2 { char d = 4; char value() const override { return d; } }; 776 | 777 | small_unique_ptr middle1 = make_unique_small(); 778 | REQUIRE(middle1->a == 1); 779 | REQUIRE(middle1->value() == 4); 780 | 781 | small_unique_ptr base1 = std::move(middle1); 782 | REQUIRE(base1->a == 1); 783 | REQUIRE(base1->value() == 4); 784 | 785 | 786 | small_unique_ptr middle2; middle2 = make_unique_small(); 787 | REQUIRE(middle2->a == 1); 788 | REQUIRE(middle2->value() == 4); 789 | 790 | small_unique_ptr base2 = std::move(middle2); 791 | REQUIRE(base2->a == 1); 792 | REQUIRE(base2->value() == 4); 793 | } 794 | 795 | TEST_CASE("abstract_base", "[small_unique_ptr]") 796 | { 797 | struct ABase { virtual int value() const = 0; virtual ~ABase() = default; }; 798 | struct ADerived : ABase { int value() const override { return 12; } }; 799 | 800 | small_unique_ptr p = make_unique_small(); 801 | REQUIRE(p->value() == 12); 802 | 803 | small_unique_ptr pc = std::move(p); 804 | REQUIRE(pc->value() == 12); 805 | REQUIRE(p == nullptr); 806 | } 807 | 808 | TEST_CASE("alignment_simple", "[small_unique_ptr]") 809 | { 810 | small_unique_ptr ps = make_unique_small(); 811 | small_unique_ptr pl = make_unique_small(); 812 | 813 | REQUIRE((std::bit_cast(std::addressof(*ps)) % alignof(SmallPOD)) == 0); 814 | REQUIRE((std::bit_cast(std::addressof(*pl)) % alignof(LargePOD)) == 0); 815 | } 816 | 817 | TEST_CASE("alignment_array", "[small_unique_ptr]") 818 | { 819 | small_unique_ptr ps = make_unique_small(4); 820 | small_unique_ptr pl = make_unique_small(2); 821 | 822 | REQUIRE((std::bit_cast(std::addressof(ps[0])) % alignof(SmallPOD)) == 0); 823 | REQUIRE((std::bit_cast(std::addressof(pl[0])) % alignof(LargePOD)) == 0); 824 | } 825 | 826 | TEST_CASE("alignment_poly", "[small_unique_ptr]") 827 | { 828 | struct alignas(16) SmallAlign { virtual ~SmallAlign() = default; }; 829 | struct alignas(128) LargeAlign : SmallAlign {}; 830 | 831 | small_unique_ptr p = make_unique_small(); 832 | 833 | REQUIRE((std::bit_cast(std::addressof(*p)) % alignof(LargeAlign)) == 0); 834 | } 835 | 836 | TEST_CASE("const_unique_ptr", "[small_unique_ptr]") 837 | { 838 | const small_unique_ptr p = make_unique_small(3); 839 | *p = 2; 840 | 841 | REQUIRE(*p == 2); 842 | } 843 | --------------------------------------------------------------------------------