├── .clang-format ├── .gitattributes ├── .github └── workflows │ ├── cmake.yml │ └── doc.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── Findoup.cmake └── oup-config.cmake.in ├── codecov.yml ├── doc └── dox.conf ├── include └── oup │ └── observable_unique_ptr.hpp ├── oup.sublime-project └── tests ├── CMakeLists.txt ├── compile_test_copy_assign.cpp ├── compile_test_copy_const.cpp ├── compile_test_good.cpp ├── compile_test_implicit_const_base_to_derived1.cpp ├── compile_test_implicit_const_base_to_derived2.cpp ├── compile_test_implicit_const_base_to_derived3.cpp ├── compile_test_observer_assign_raw.cpp ├── compile_test_observer_construct_raw.cpp ├── compile_test_sealed_assign_raw.cpp ├── compile_test_sealed_construct_raw.cpp ├── compile_test_sealed_release.cpp ├── compile_test_sealed_reset.cpp ├── memory_tracker.cpp ├── memory_tracker.hpp ├── runtime_tests_lifetime.cpp ├── runtime_tests_make_observable.cpp ├── runtime_tests_observer_assignment_copy.cpp ├── runtime_tests_observer_assignment_from_owner.cpp ├── runtime_tests_observer_assignment_move.cpp ├── runtime_tests_observer_cast_copy.cpp ├── runtime_tests_observer_cast_move.cpp ├── runtime_tests_observer_comparison.cpp ├── runtime_tests_observer_construction.cpp ├── runtime_tests_observer_construction_copy.cpp ├── runtime_tests_observer_construction_from_owner.cpp ├── runtime_tests_observer_construction_move.cpp ├── runtime_tests_observer_from_this.cpp ├── runtime_tests_observer_misc.cpp ├── runtime_tests_owner_assignment_move.cpp ├── runtime_tests_owner_cast_move.cpp ├── runtime_tests_owner_comparison.cpp ├── runtime_tests_owner_construction.cpp ├── runtime_tests_owner_misc.cpp ├── size_benchmark.cpp ├── speed_benchmark.cpp ├── speed_benchmark_common.hpp ├── speed_benchmark_utility.cpp ├── speed_benchmark_utility2.cpp ├── testing.hpp ├── tests_common.cpp └── tests_common.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: AlwaysBreak 5 | AlignConsecutiveAssignments: 'true' 6 | AlignConsecutiveDeclarations: 'true' 7 | AlignEscapedNewlines: Right 8 | AlignOperands: 'true' 9 | AlignTrailingComments: 'false' 10 | AllowAllArgumentsOnNextLine: 'true' 11 | AllowAllConstructorInitializersOnNextLine: 'true' 12 | AllowAllParametersOfDeclarationOnNextLine: 'true' 13 | AllowShortBlocksOnASingleLine: 'false' 14 | AllowShortCaseLabelsOnASingleLine: 'true' 15 | AllowShortFunctionsOnASingleLine: Empty 16 | AllowShortIfStatementsOnASingleLine: Never 17 | AllowShortLambdasOnASingleLine: All 18 | AllowShortLoopsOnASingleLine: 'false' 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: 'false' 21 | AlwaysBreakTemplateDeclarations: 'Yes' 22 | BinPackArguments: 'true' 23 | BinPackParameters: 'false' 24 | BreakBeforeBinaryOperators: None 25 | BreakBeforeBraces: Attach 26 | BreakBeforeTernaryOperators: 'true' 27 | BreakConstructorInitializers: AfterColon 28 | BreakInheritanceList: AfterColon 29 | BreakStringLiterals: 'true' 30 | ColumnLimit: '100' 31 | CompactNamespaces: 'true' 32 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 33 | Cpp11BracedListStyle: 'true' 34 | DerivePointerAlignment: 'false' 35 | DisableFormat: 'false' 36 | FixNamespaceComments: 'true' 37 | IncludeBlocks: Regroup 38 | IndentPPDirectives: AfterHash 39 | IndentWidth: '4' 40 | IndentWrappedFunctionNames: 'false' 41 | KeepEmptyLinesAtTheStartOfBlocks: 'true' 42 | Language: Cpp 43 | MaxEmptyLinesToKeep: '1' 44 | PointerAlignment: Left 45 | ReflowComments: 'true' 46 | CommentPragmas: '(\\param |\\returns |\\warning |\\ingroup |\\author |\\date |\\related |\\relates |\\relatesalso |\\deprecated |\\image |\\return |\\brief |\\attention |\\copydoc |\\addtogroup |\\todo |\\tparam |\\see |\\note |\\skip |\\skipline |\\until |\\line |\\dontinclude |\\include|@function|@treturn|@tparam|@classmod|@module|@see)' 47 | SortIncludes: 'true' 48 | SortUsingDeclarations: 'true' 49 | SpaceAfterCStyleCast: 'false' 50 | SpaceAfterLogicalNot: 'false' 51 | SpaceAfterTemplateKeyword: 'false' 52 | SpaceBeforeAssignmentOperators: 'true' 53 | SpaceBeforeCpp11BracedList: 'false' 54 | SpaceBeforeCtorInitializerColon: 'true' 55 | SpaceBeforeInheritanceColon: 'true' 56 | SpaceBeforeParens: ControlStatements 57 | SpaceBeforeRangeBasedForLoopColon: 'true' 58 | SpaceInEmptyParentheses: 'false' 59 | SpacesBeforeTrailingComments: '1' 60 | SpacesInAngles: 'false' 61 | SpacesInCStyleCastParentheses: 'false' 62 | SpacesInParentheses: 'false' 63 | SpacesInSquareBrackets: 'false' 64 | Standard: Cpp11 65 | TabWidth: '4' 66 | UseTab: Never 67 | 68 | ... 69 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.bat eol=crlf 4 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | env: 11 | EM_VERSION: 3.1.35 12 | EM_CACHE_FOLDER: 'emsdk-cache' 13 | CODECOV_TOKEN: '99959e57-0b92-48b4-bf55-559d43d41b58' 14 | 15 | jobs: 16 | build: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | platform: 21 | - { name: Ubuntu GCC, os: ubuntu-latest, compiler: g++, arch: "64", cmakepp: "", flags: "-DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS='--coverage'"} 22 | - { name: Ubuntu Clang, os: ubuntu-latest, compiler: clang++, arch: "64", cmakepp: "", flags: "-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS='-stdlib=libc++'"} 23 | - { name: Windows 32, os: windows-latest, compiler: vs2019, arch: "32", cmakepp: "", flags: "-A Win32"} 24 | - { name: Windows 64, os: windows-latest, compiler: vs2019, arch: "64", cmakepp: "", flags: "-A x64"} 25 | - { name: MacOS, os: macos-latest, compiler: clang++, arch: "64", cmakepp: "", flags: ""} 26 | - { name: WebAssembly, os: ubuntu-latest, compiler: em++, arch: "32", cmakepp: "emcmake", flags: "-DCMAKE_CXX_FLAGS='-s DISABLE_EXCEPTION_CATCHING=0' -DCMAKE_CXX_LINK_FLAGS='-s STACK_SIZE=5MB' -DCMAKE_CROSSCOMPILING_EMULATOR=node"} 27 | build-type: 28 | - Release 29 | - Debug 30 | 31 | name: ${{matrix.platform.name}} ${{matrix.build-type}} 32 | runs-on: ${{matrix.platform.os}} 33 | 34 | steps: 35 | - name: Checkout code 36 | uses: actions/checkout@v4 37 | with: 38 | fetch-depth: 2 # necessary for codecov bash uploader 39 | submodules: 'recursive' 40 | 41 | - name: Setup Clang 42 | if: matrix.platform.compiler == 'clang++' && matrix.platform.os == 'ubuntu-latest' 43 | run: sudo apt install clang libc++-dev libc++abi-dev 44 | 45 | - name: Setup Emscripten cache 46 | if: matrix.platform.compiler == 'em++' 47 | id: cache-system-libraries 48 | uses: actions/cache@v4 49 | with: 50 | path: ${{env.EM_CACHE_FOLDER}} 51 | key: ${{env.EM_VERSION}}-${{matrix.platform.name}}-${{matrix.build-type}} 52 | 53 | - name: Setup Emscripten 54 | if: matrix.platform.compiler == 'em++' 55 | uses: mymindstorm/setup-emsdk@v14 56 | with: 57 | version: ${{env.EM_VERSION}} 58 | actions-cache-folder: ${{env.EM_CACHE_FOLDER}} 59 | 60 | # GCC 9 has a bug which prevents compilation of the testing framework, so switch to GCC 10. 61 | - name: Setup GCC 62 | if: matrix.platform.compiler == 'g++' 63 | run: | 64 | sudo apt install g++-10 65 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 --slave /usr/bin/g++ g++ /usr/bin/g++-10 --slave /usr/bin/gcov gcov /usr/bin/gcov-10 66 | sudo update-alternatives --set gcc /usr/bin/gcc-10 67 | 68 | - name: Create Build Environment 69 | run: cmake -E make_directory ${{github.workspace}}/build 70 | 71 | - name: Configure CMake 72 | shell: bash 73 | working-directory: ${{github.workspace}}/build 74 | run: ${{matrix.platform.cmakepp}} cmake .. -DCMAKE_BUILD_TYPE=${{matrix.build-type}} ${{matrix.platform.flags}} -DOUP_DO_TEST=1 75 | 76 | - name: Build 77 | shell: bash 78 | working-directory: ${{github.workspace}}/build 79 | run: cmake --build . --config ${{matrix.build-type}} --parallel 2 80 | 81 | - name: Test 82 | shell: bash 83 | working-directory: ${{github.workspace}}/build 84 | run: cmake --build . --config ${{matrix.build-type}} --target oup_runtime_tests_run 85 | 86 | - name: Compute Code Coverage 87 | if: runner.os == 'Linux' && matrix.platform.compiler == 'g++' && matrix.build-type == 'Debug' 88 | run: | 89 | gcov ${{github.workspace}}/build/tests/CMakeFiles/oup_runtime_tests.dir/*.gcda 90 | ls | grep '.gcov' | grep -v observable_unique_ptr | xargs -d"\n" rm 91 | bash <(curl -s https://codecov.io/bash) 92 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Doc 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: Build documentation 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Get dependencies 18 | run: sudo apt-get install doxygen 19 | 20 | - name: Build C++ documentation 21 | working-directory: ${{github.workspace}}/doc 22 | shell: bash 23 | run: doxygen dox.conf 24 | 25 | - name: Upload as an Artifact 26 | uses: actions/upload-artifact@v4 27 | with: 28 | name: docs 29 | path: ${{github.workspace}}/doc/html 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | build_wasm/ 3 | build_clang/ 4 | doc/html/ 5 | oup.sublime-workspace 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8..3.31) 2 | 3 | # Setup main project 4 | project(oup LANGUAGES CXX VERSION 0.7.3) 5 | 6 | # Create library (header-only) 7 | add_library(oup INTERFACE) 8 | add_library(oup::oup ALIAS oup) 9 | set_target_properties(oup PROPERTIES EXPORT_NAME oup::oup) 10 | 11 | target_sources(oup INTERFACE 12 | $ 13 | $) 14 | target_include_directories(oup INTERFACE 15 | $ 16 | $) 17 | target_compile_features(oup INTERFACE cxx_std_17) 18 | 19 | # Setup install target and exports 20 | install(FILES ${PROJECT_SOURCE_DIR}/include/oup/observable_unique_ptr.hpp 21 | DESTINATION ${CMAKE_INSTALL_PREFIX}/include/oup) 22 | install(TARGETS oup EXPORT oup-targets) 23 | 24 | install(EXPORT oup-targets 25 | DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/oup COMPONENT Development) 26 | 27 | export(EXPORT oup-targets) 28 | 29 | # Setup CMake config file 30 | include(CMakePackageConfigHelpers) 31 | configure_package_config_file( 32 | "${PROJECT_SOURCE_DIR}/cmake/oup-config.cmake.in" 33 | "${PROJECT_BINARY_DIR}/oup-config.cmake" 34 | INSTALL_DESTINATION ${CMAKE_INSTALL_PREFIX}/lib 35 | NO_CHECK_REQUIRED_COMPONENTS_MACRO 36 | NO_SET_AND_CHECK_MACRO) 37 | 38 | install(FILES 39 | "${PROJECT_BINARY_DIR}/oup-config.cmake" 40 | DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/oup COMPONENT Development) 41 | 42 | # Setup tests 43 | if (OUP_DO_TEST) 44 | enable_testing() 45 | add_subdirectory(tests) 46 | endif() 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Corentin Schreiber 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # observable_unique_ptr, observable_sealed_ptr, observer_ptr 2 | 3 | ![Build Status](https://github.com/cschreib/observable_unique_ptr/actions/workflows/cmake.yml/badge.svg) ![Docs Build Status](https://github.com/cschreib/observable_unique_ptr/actions/workflows/doc.yml/badge.svg) [![codecov](https://codecov.io/gh/cschreib/observable_unique_ptr/branch/main/graph/badge.svg?token=8C11D2U94D)](https://codecov.io/gh/cschreib/observable_unique_ptr) 4 | 5 | Built and tested on: 6 | - Linux (GCC/clang) 7 | - Windows (MSVC 32/64) 8 | - MacOS (clang) 9 | - WebAssembly (Emscripten) 10 | 11 | **Table of content:** 12 | 13 | 14 | - [Introduction](#introduction) 15 | - [Usage](#usage) 16 | - [enable_observer_from_this](#enable_observer_from_this) 17 | - [Policies](#policies) 18 | - [Limitations](#limitations) 19 | - [Thread safety](#thread-safety) 20 | - [Comparison spreadsheet](#comparison-spreadsheet) 21 | - [Speed benchmarks](#speed-benchmarks) 22 | - [Alternative implementation](#alternative-implementation) 23 | 24 | 25 | 26 | 27 | ## Introduction 28 | 29 | This is a small header-only library, providing the unique-ownership smart pointers `observable_unique_ptr` and `observable_sealed_ptr` that can be observed with non-owning pointers `observer_ptr`. This is a mixture of `std::unique_ptr` and `std::shared_ptr`: it borrows the unique-ownership semantic of `std::unique_ptr` (movable, non-copiable), but allows creating `observer_ptr` to monitor the lifetime of the pointed object (like `std::weak_ptr` for `std::shared_ptr`). 30 | 31 | The only difference between `observable_unique_ptr` and `observable_sealed_ptr` is that the former can release ownership, while the latter cannot. Disallowing release of ownership enables allocation optimizations. Therefore, the recommendation is to use `observable_sealed_ptr` unless release of ownership is required. 32 | 33 | These pointers are useful for cases where the shared-ownership of `std::shared_ptr` is not desirable, e.g., when lifetime must be carefully controlled and not be allowed to extend, yet non-owning/weak/observer references to the object may exist after the object has been deleted. 34 | 35 | Note: Because of the unique ownership model, observer pointers cannot extend the lifetime of the pointed object, hence this library provides less safety compared to `std::shared_ptr`/`std::weak_ptr`. See the [Thread safety](#thread-safety) section. This is also true of `std::unique_ptr`, and is a fundamental limitation of unique ownership. If this is an issue, simply use `std::shared_ptr`/`std::weak_ptr`. 36 | 37 | 38 | ## Usage 39 | 40 | This is a header-only library requiring a C++17-compliant compiler. You have multiple ways to set it up: 41 | - just include this repository as a submodule in your own git repository and use CMake `add_subdirectory` (or use CMake `FetchContent`), then link with `target_link_libraries( PUBLIC oup::oup)`. 42 | - download the header and include it in your own sources. 43 | 44 | From there, include the single header ``, and directly use the smart pointer in your own code: 45 | 46 | ```c++ 47 | #include 48 | 49 | #include 50 | #include 51 | #include 52 | 53 | int main() { 54 | // Non-owning pointer that will outlive the object 55 | oup::observer_ptr obs_ptr; 56 | 57 | { 58 | // Sealed (unique) pointer that owns the object 59 | auto owner_ptr = oup::make_observable_sealed("hello"); 60 | 61 | // A sealed pointer cannot be copied but it can be moved 62 | // oup::observable_sealed_ptr owner_copied = owner_ptr; // error! 63 | // oup::observable_sealed_ptr owner_moved = std::move(owner_ptr); // OK 64 | 65 | // Make the observer pointer point to the object 66 | obs_ptr = owner_ptr; 67 | 68 | // The observer pointer is now valid 69 | assert(!obs_ptr.expired()); 70 | 71 | // It can be used like a regular raw pointer 72 | assert(obs_ptr != nullptr); 73 | std::cout << *obs_ptr << std::endl; 74 | 75 | // An observer pointer can be copied and moved 76 | // oup::observer_ptr obs_copied = obs_ptr; // OK 77 | // oup::observer_ptr obs_moved = std::move(obs_ptr); // OK 78 | } 79 | 80 | // The sealed pointer has gone out of scope, the object is deleted, 81 | // the observer pointer is now null. 82 | assert(obs_ptr.expired()); 83 | assert(obs_ptr == nullptr); 84 | 85 | return 0; 86 | } 87 | ``` 88 | 89 | 90 | ## enable_observer_from_this 91 | 92 | As with `std::shared_ptr`/`std::weak_ptr`, if you need to obtain an observer pointer to an object when you only have `this` (i.e., from a member function), you can inherit from `oup::enable_observer_from_this_unique` or `oup::enable_observer_from_this_sealed` (depending on the type of the owner pointer) to gain access to the `observer_from_this()` member function. Contrary to `std::enable_shared_from_this`, this function is `noexcept` and is able to return a valid observer pointer at all times, even if the object is being constructed or is not owned by a unique or sealed pointer. Also contrary to `std::enable_shared_from_this`, this feature naturally supports multiple inheritance. 93 | 94 | To achieve this, the price to pay is that `oup::enable_observer_from_this_unique` uses virtual inheritance, while `oup::enable_observer_from_this_sealed` requires `T`'s constructor to take a control block as input (thereby preventing `T` from being default-constructible, copiable, or movable). If needed, these trade-offs can be controlled by policies, see below. 95 | 96 | 97 | ## Policies 98 | 99 | Similarly to `std::string` and `std::basic_string`, this library provides both "convenience" types (`oup::observable_unique_ptr`, `oup::observable_sealed_ptr`, `oup::observer_ptr`, `oup::enable_observable_from_this_unique`, `oup::enable_observable_from_this_sealed`) and "generic" types (`oup::basic_observable_ptr`, `oup::basic_observer_ptr`, `oup::basic_enable_observable_from_this`). 100 | 101 | If the trade-offs chosen to defined the "convenience" types are not appropriate for your use cases, they can be fine-tuned using the generic classes and providing your own choice of policies. Please refer to the documentation for more information on policies. In particular, policies will control most of the API and behavior of the `enable_observable_from_this` feature, as well as allowing you to tune the size of the reference counting object (speed/memory trade-off). 102 | 103 | 104 | ## Limitations 105 | 106 | The following limitations are features that were not implemented simply because of lack of motivation. 107 | 108 | - this library is not thread-safe, contrary to `std::shared_ptr`. See the [Thread safety](#thread-safety) section for more info. 109 | - this library does not support pointers to arrays, but `std::unique_ptr` and `std::shared_ptr` both do. 110 | - this library does not support custom allocators, but `std::shared_ptr` does. 111 | 112 | 113 | ## Thread safety 114 | 115 | This library uses reference counting to handle observable and observer pointers. The current implementation does not use any synchronization mechanism (mutex, lock, etc.) to wrap operations on the reference counter. Therefore, it is unsafe to have an observable pointer on one thread being observed by observer pointers on another thread. 116 | 117 | The above could be fixed in the future by adding a configurable policy to enable or disable synchronization. However, the unique ownership model still imposes fundamental limitations on thread safety: an observer pointer cannot extend the lifetime of the observed object (like `std::weak_ptr::lock()` would do). The only guarantee that could be offered is the following: if `expired()` returns true, the observed pointer is guaranteed to remain `nullptr` forever, with no race condition. If `expired()` returns false, the pointer could still expire on the next instant, which can lead to race conditions. To completely avoid race conditions, you will need to add explicit synchronization around your object. 118 | 119 | Finally, because this library uses no global state (beyond the standard allocator, which is thread-safe), it is perfectly fine to use it in a threaded application, provided that all observer pointers for a given object live on the same thread as the object itself. 120 | 121 | 122 | ## Comparison spreadsheet 123 | 124 | In this comparison spreadsheet, the raw pointer `T*` is assumed to never be owning, and used only to observe an existing object (which may or may not have been deleted). Unless otherwise specified, the stack and heap sizes were measured with gcc 9.4.0 and libstdc++-9. 125 | 126 | Labels: 127 | - raw: `T*` 128 | - unique: `std::unique_ptr` 129 | - weak: `std::weak_ptr` 130 | - shared: `std::shared_ptr` 131 | - observer: `oup::observer_ptr` 132 | - obs_unique: `oup::observable_unique_ptr` 133 | - obs_sealed: `oup::observable_sealed_ptr` 134 | 135 | | Pointer | raw | weak | observer | unique | shared | obs_unique | obs_sealed | 136 | |--------------------------|------|--------|----------|--------|--------|------------|------------| 137 | | Owning | no | no | no | yes | yes | yes | yes | 138 | | Releasable | N/A | N/A | N/A | yes | no | yes | no | 139 | | Observable deletion | no | yes | yes | yes | yes | yes | yes | 140 | | Thread-safe | no | yes | no | no | yes | no | no | 141 | | Atomic | yes | no(1) | no | no | no(1) | no | no | 142 | | Support arrays | yes | yes | no | yes | yes | no | no | 143 | | Support custom allocator | N/A | yes | no | yes | yes | no | no | 144 | | Support custom deleter | N/A | N/A | N/A | yes | yes(2) | yes | no | 145 | | Max number of observers | inf. | ?(3) | 2^31 - 1 | 1 | ?(3) | 1 | 1 | 146 | | Number of heap alloc. | 0 | 0 | 0 | 1 | 1/2(4) | 2 | 1 | 147 | | Size in bytes (64 bit) | | | | | | | | 148 | | - Stack (per instance) | 8 | 16 | 16 | 8 | 16 | 16 | 16 | 149 | | - Heap (shared) | 0 | 0 | 0 | 0 | 24(5) | 4 | 4(6) | 150 | | - Total | 8 | 16 | 16 | 8 | 40 | 20 | 20 | 151 | | Size in bytes (32 bit) | | | | | | | | 152 | | - Stack (per instance) | 4 | 8 | 8 | 4 | 8 | 8 | 8 | 153 | | - Heap (shared) | 0 | 0 | 0 | 0 | 16 | 4 | 4 | 154 | | - Total | 4 | 8 | 8 | 4 | 24 | 12 | 12 | 155 | 156 | Notes: 157 | 158 | - (1) Yes if using `std::atomic>` and `std::atomic>`. 159 | - (2) Not if using `std::make_shared()`. 160 | - (3) Not defined by the C++ standard. In practice, libstdc++ stores its reference count on an `_Atomic_word`, which for a common 64bit linux platform is a 4 byte signed integer, hence the limit will be 2^31 - 1. Microsoft's STL uses `_Atomic_counter_t`, which for a 64bit Windows platform is 4 bytes unsigned integer, hence the limit will be 2^32 - 1. 161 | - (4) 2 by default, or 1 if using `std::make_shared()`. 162 | - (5) When using `std::make_shared()`, this can get as low as 16 bytes, or larger than 24 bytes, depending on the size and alignment requirements of the object type. This behavior is shared by libstdc++ and MS-STL. 163 | - (6) Can get larger than 4 depending on the alignment requirements of the object type. 164 | 165 | 166 | ## Speed benchmarks 167 | 168 | Labels are the same as in the comparison spreadsheet. The speed benchmarks were compiled with all optimizations turned on (except LTO). Speed is measured relative to `std::unique_ptr` used as owner pointer, and `T*` used as observer pointer, which should be the fastest possible implementation (but obviously the one with least safety). 169 | 170 | You can run the benchmarks yourself, they are located in `tests/speed_benchmark.cpp`. The benchmark executable runs tests for three object types: `int`, `float`, `std::string`, and `std::array`, to simulate objects of various allocation cost. The timings below are the median values measured across all object types, which should be most relevant to highlight the overhead from the pointer itself (and erases flukes from the benchmarking framework). In real life scenarios, the actual measured overhead will be substantially lower, as actual business logic is likely to dominate the time budget. 171 | 172 | Detail of the benchmarks: 173 | - Create owner empty: default-construct an owner pointer (to nullptr). 174 | - Create owner: construct an owner pointer by taking ownership of an existing object. 175 | - Create owner factory: construct an owner pointer using `std::make_*` or `oup::make_*` factory functions. 176 | - Dereference owner: get a reference to the underlying owned object from an owner pointer. 177 | - Create observer empty: default-construct an observer pointer (to nullptr). 178 | - Create observer: construct an observer pointer from an owner pointer. 179 | - Create observer copy: construct a new observer pointer from another observer pointer. 180 | - Dereference observer: get a reference to the underlying object from an observer pointer. 181 | 182 | The benchmarks were last ran for oup v0.7.1. 183 | 184 | *Compiler: gcc 9.4.0, std: libstdc++-9, OS: linux 5.15.0, CPU: Ryzen 5 2600:* 185 | 186 | | Pointer | raw/unique | weak/shared | observer/obs_unique | observer/obs_sealed | 187 | | --- | --- | --- | --- | --- | 188 | | Create owner empty | 1 | 1.1 | 1.1 | 1.2 | 189 | | Create owner | 1 | 2.1 | 1.7 | N/A | 190 | | Create owner factory | 1 | 1.3 | 1.7 | 1.1 | 191 | | Dereference owner | 1 | 1.0 | 1.0 | 1.1 | 192 | | Create observer empty | 1 | 1.1 | 1.2 | 1.2 | 193 | | Create observer | 1 | 1.6 | 1.6 | 1.6 | 194 | | Create observer copy | 1 | 1.7 | 1.6 | 1.6 | 195 | | Dereference observer | 1 | 3.5 | 1.0 | 1.0 | 196 | 197 | *Compiler: MSVC 16.11.3, std: MS-STL, OS: Windows 10.0.19043, CPU: i7-7800x:* 198 | 199 | | Pointer | raw/unique | weak/shared | observer/obs_unique | observer/obs_sealed | 200 | | --- | --- | --- | --- | --- | 201 | | Create owner empty | 1 | 1.4 | 1.8 | 1.5 | 202 | | Create owner | 1 | 2.2 | 2.9 | N/A | 203 | | Create owner factory | 1 | 1.2 | 2.2 | 0.9 | 204 | | Dereference owner | 1 | 0.7 | 1.3 | 1.0 | 205 | | Create observer empty | 1 | 1.6 | 1.0 | 0.8 | 206 | | Create observer | 1 | 5.3 | 1.6 | 1.6 | 207 | | Create observer copy | 1 | 5.3 | 1.4 | 1.5 | 208 | | Dereference observer | 1 | 9.4 | 1.4 | 0.8 | 209 | 210 | *Compiler: Emscripten 2.0.34, std: libc++, OS: Node.js 14.15.5 + linux kernel 5.1.0, CPU: Ryzen 5 2600:* 211 | 212 | | Pointer | raw/unique | weak/shared | observer/obs_unique | observer/obs_sealed | 213 | | --- | --- | --- | --- | --- | 214 | | Create owner empty | 1 | 6.9 | 1.1 | 1.0 | 215 | | Create owner | 1 | 1.8 | 1.6 | N/A | 216 | | Create owner factory | 1 | 1.2 | 1.7 | 1.0 | 217 | | Dereference owner | 1 | 1.0 | 1.0 | 1.0 | 218 | | Create observer empty | 1 | 11.4 | 1.6 | 1.6 | 219 | | Create observer | 1 | 14.8 | 2.3 | 2.3 | 220 | | Create observer copy | 1 | 14.9 | 2.3 | 2.5 | 221 | | Dereference observer | 1 | 38.7 | 1.0 | 1.0 | 222 | 223 | 224 | ## Alternative implementation 225 | 226 | An alternative implementation of an "observable unique pointer" can be found [here](https://www.codeproject.com/articles/1011134/smart-observers-to-use-with-unique-ptr). It does not compile out of the box with gcc unfortunately and lacks certain features (their `make_observable` always performs two allocations). Have a look to check if this better suits your needs. 227 | -------------------------------------------------------------------------------- /cmake/Findoup.cmake: -------------------------------------------------------------------------------- 1 | find_path(OUP_INCLUDE_DIR observable_unique_ptr.hpp 2 | HINTS ${OUP_DIR} 3 | PATH_SUFFIXES oup include/oup 4 | ) 5 | 6 | set(OUP_INCLUDE_DIRS ${OUP_INCLUDE_DIR}) 7 | 8 | include(FindPackageHandleStandardArgs) 9 | 10 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(oup REQUIRED_VARS OUP_INCLUDE_DIRS) 11 | 12 | mark_as_advanced(OUP_INCLUDE_DIR) 13 | 14 | if (OUP_FOUND) 15 | if(NOT TARGET oup::oup) 16 | add_library(oup::oup IMPORTED INTERFACE) 17 | target_include_directories(oup::oup INTERFACE "${OUP_INCLUDE_DIRS}") 18 | endif() 19 | endif() 20 | -------------------------------------------------------------------------------- /cmake/oup-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/oup-targets.cmake") 4 | 5 | if (NOT TARGET oup) 6 | add_library(oup INTERFACE IMPORTED) 7 | # Equivalent to target_link_libraries INTERFACE, but compatible with CMake 3.10 8 | set_target_properties(oup PROPERTIES INTERFACE_LINK_LIBRARIES oup::oup) 9 | endif () 10 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "90...100" 8 | 9 | parsers: 10 | gcov: 11 | branch_detection: 12 | conditional: yes 13 | loop: yes 14 | method: no 15 | macro: no 16 | 17 | ignore: 18 | - "tests" 19 | 20 | comment: 21 | layout: "reach,diff,flags,files,footer" 22 | behavior: default 23 | require_changes: no 24 | -------------------------------------------------------------------------------- /oup.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": ".", 6 | } 7 | ], 8 | "settings": 9 | { 10 | "cmake": 11 | { 12 | "build_folder": "$folder/build", 13 | }, 14 | "ClangFormat": 15 | { 16 | "format_on_save": true, 17 | }, 18 | }, 19 | "build_systems": 20 | [ 21 | { 22 | "file_regex": "(.+[^:]):(\\d+):(\\d+): (?:fatal )?((?:error|warning): .+)$", 23 | "name": "oup (Linux)", 24 | "shell_cmd": "make -j12", 25 | "syntax": "Packages/CMakeBuilder/Syntax/Make.sublime-syntax", 26 | "variants": 27 | [ 28 | { 29 | "name": "clean", 30 | "shell_cmd": "make -j12 clean", 31 | }, 32 | { 33 | "name": "install", 34 | "shell_cmd": "make -j12 install", 35 | }, 36 | { 37 | "name": "install/local", 38 | "shell_cmd": "make -j12 install/local", 39 | }, 40 | { 41 | "name": "install/strip", 42 | "shell_cmd": "make -j12 install/strip", 43 | }, 44 | { 45 | "name": "list_install_components", 46 | "shell_cmd": "make -j12 list_install_components", 47 | }, 48 | { 49 | "name": "rebuild_cache", 50 | "shell_cmd": "make -j12 rebuild_cache", 51 | }, 52 | { 53 | "name": "test", 54 | "shell_cmd": "make -j12 test", 55 | }, 56 | { 57 | "name": "oup_runtime_tests_run", 58 | "shell_cmd": "make -j12 oup_runtime_tests_run", 59 | }, 60 | { 61 | "name": "oup_runtime_tests", 62 | "shell_cmd": "make -j12 oup_runtime_tests", 63 | }, 64 | { 65 | "name": "oup_size_benchmark", 66 | "shell_cmd": "make -j12 oup_size_benchmark", 67 | }, 68 | { 69 | "name": "oup_speed_benchmark", 70 | "shell_cmd": "make -j12 oup_speed_benchmark", 71 | }, 72 | { 73 | "name": "snitch", 74 | "shell_cmd": "make -j12 snitch", 75 | }, 76 | ], 77 | "working_dir": "$folder/build", 78 | } 79 | ], 80 | } 81 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(add_platform_definitions TARGET) 2 | target_compile_features(${TARGET} INTERFACE cxx_std_20) 3 | if(CMAKE_SYSTEM_NAME MATCHES "Emscripten") 4 | target_compile_definitions(${TARGET} PRIVATE OUP_PLATFORM_WASM) 5 | target_compile_definitions(${TARGET} PRIVATE OUP_COMPILER_EMSCRIPTEN) 6 | target_compile_definitions(${TARGET} PRIVATE OUP_COMPILER_LLVM) 7 | elseif (APPLE) 8 | target_compile_definitions(${TARGET} PRIVATE OUP_PLATFORM_OSX) 9 | elseif (UNIX) 10 | target_compile_definitions(${TARGET} PRIVATE OUP_PLATFORM_LINUX) 11 | elseif (WIN32) 12 | target_compile_definitions(${TARGET} PRIVATE OUP_PLATFORM_WINDOWS) 13 | if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 14 | target_compile_definitions(${TARGET} PRIVATE OUP_COMPILER_MSVC) 15 | endif() 16 | endif() 17 | 18 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 19 | target_compile_options(${TARGET} PRIVATE -Wall) 20 | target_compile_options(${TARGET} PRIVATE -Wextra) 21 | target_compile_options(${TARGET} PRIVATE -Werror) 22 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") 23 | target_compile_options(${TARGET} PRIVATE -Wall) 24 | target_compile_options(${TARGET} PRIVATE -Wextra) 25 | target_compile_options(${TARGET} PRIVATE -Werror) 26 | target_compile_definitions(${TARGET} PRIVATE OUP_COMPILER_LLVM) 27 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 28 | target_compile_options(${TARGET} PRIVATE /W4) 29 | target_compile_options(${TARGET} PRIVATE /WX) 30 | target_compile_options(${TARGET} PRIVATE /EHs) 31 | endif() 32 | endfunction() 33 | 34 | include(FetchContent) 35 | FetchContent_Declare(snitch 36 | GIT_REPOSITORY https://github.com/cschreib/snitch.git 37 | GIT_TAG v1.3.2) 38 | FetchContent_MakeAvailable(snitch) 39 | 40 | set(RUNTIME_TEST_FILES 41 | ${PROJECT_SOURCE_DIR}/tests/tests_common.cpp 42 | ${PROJECT_SOURCE_DIR}/tests/memory_tracker.cpp 43 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_owner_misc.cpp 44 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_owner_construction.cpp 45 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_owner_assignment_move.cpp 46 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_owner_comparison.cpp 47 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_owner_cast_move.cpp 48 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_lifetime.cpp 49 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_make_observable.cpp 50 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_misc.cpp 51 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_construction.cpp 52 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_construction_from_owner.cpp 53 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_construction_copy.cpp 54 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_construction_move.cpp 55 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_assignment_from_owner.cpp 56 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_assignment_copy.cpp 57 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_assignment_move.cpp 58 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_comparison.cpp 59 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_cast_copy.cpp 60 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_cast_move.cpp 61 | ${PROJECT_SOURCE_DIR}/tests/runtime_tests_observer_from_this.cpp) 62 | 63 | add_executable(oup_runtime_tests ${RUNTIME_TEST_FILES}) 64 | target_link_libraries(oup_runtime_tests PRIVATE oup::oup) 65 | target_link_libraries(oup_runtime_tests PRIVATE snitch::snitch) 66 | add_platform_definitions(oup_runtime_tests) 67 | 68 | add_custom_target(oup_runtime_tests_run 69 | COMMAND oup_runtime_tests 70 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 71 | SOURCES ${RUNTIME_TEST_FILES} 72 | ) 73 | set_target_properties(oup_runtime_tests_run PROPERTIES EXCLUDE_FROM_ALL True) 74 | 75 | # Compile-time error tests 76 | set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) 77 | message(STATUS "Running compile-time tests...") 78 | 79 | function(run_compile_test TEST_NAME TEST_FILE EXPECTED) 80 | try_compile(COMPILE_TEST_RESULT 81 | ${PROJECT_SOURCE_DIR}/tests 82 | SOURCES 83 | ${PROJECT_SOURCE_DIR}/tests/${TEST_FILE} 84 | ${PROJECT_SOURCE_DIR}/tests/tests_common.cpp 85 | CMAKE_FLAGS 86 | "-DINCLUDE_DIRECTORIES=${PROJECT_SOURCE_DIR}/include" 87 | "-DCMAKE_CXX_STANDARD=20") 88 | 89 | if(COMPILE_TEST_RESULT STREQUAL EXPECTED) 90 | message(STATUS "Test ${TEST_NAME} passed.") 91 | else() 92 | message(WARNING "FAILED test: ${TEST_NAME}, expected ${EXPECTED} and got ${COMPILE_TEST_RESULT}") 93 | endif() 94 | endfunction() 95 | 96 | run_compile_test("does_compilation_work" compile_test_good.cpp TRUE) 97 | run_compile_test("is_copy_constructor_allowed" compile_test_copy_const.cpp FALSE) 98 | run_compile_test("is_copy_assignment_allowed" compile_test_copy_assign.cpp FALSE) 99 | run_compile_test("is_implicit_constructor_base_to_derived_allowed_acquire" compile_test_implicit_const_base_to_derived1.cpp FALSE) 100 | run_compile_test("is_implicit_constructor_base_to_derived_allowed_move" compile_test_implicit_const_base_to_derived2.cpp FALSE) 101 | run_compile_test("is_implicit_constructor_base_to_derived_allowed_move_with_deleter" compile_test_implicit_const_base_to_derived3.cpp FALSE) 102 | run_compile_test("is_observer_construct_raw_allowed" compile_test_observer_construct_raw.cpp FALSE) 103 | run_compile_test("is_observer_assign_raw_allowed" compile_test_observer_assign_raw.cpp FALSE) 104 | run_compile_test("is_acquire_construct_raw_allowed" compile_test_sealed_construct_raw.cpp FALSE) 105 | run_compile_test("is_acquire_assign_raw_allowed" compile_test_sealed_assign_raw.cpp FALSE) 106 | run_compile_test("is_sealed_release_allowed" compile_test_sealed_release.cpp FALSE) 107 | run_compile_test("is_sealed_reset_allowed" compile_test_sealed_reset.cpp FALSE) 108 | 109 | message(STATUS "Running compile-time tests ended.") 110 | 111 | # Benchmarks 112 | add_executable(oup_size_benchmark 113 | ${PROJECT_SOURCE_DIR}/tests/memory_tracker.cpp 114 | ${PROJECT_SOURCE_DIR}/tests/size_benchmark.cpp) 115 | target_link_libraries(oup_size_benchmark PRIVATE oup::oup) 116 | add_platform_definitions(oup_size_benchmark) 117 | 118 | add_executable(oup_speed_benchmark 119 | ${PROJECT_SOURCE_DIR}/tests/speed_benchmark.cpp 120 | ${PROJECT_SOURCE_DIR}/tests/speed_benchmark_utility.cpp 121 | ${PROJECT_SOURCE_DIR}/tests/speed_benchmark_utility2.cpp) 122 | target_link_libraries(oup_speed_benchmark PRIVATE oup::oup) 123 | add_platform_definitions(oup_speed_benchmark) 124 | -------------------------------------------------------------------------------- /tests/compile_test_copy_assign.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_unique_ptr ptr_orig(new test_object); 5 | oup::observable_unique_ptr ptr; 6 | ptr = ptr_orig; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/compile_test_copy_const.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_unique_ptr ptr_orig(new test_object); 5 | oup::observable_unique_ptr ptr(ptr_orig); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/compile_test_good.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_unique_ptr ptr_orig(new test_object); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/compile_test_implicit_const_base_to_derived1.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_unique_ptr ptr(new test_object); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/compile_test_implicit_const_base_to_derived2.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_unique_ptr ptr_orig(new test_object); 5 | oup::observable_unique_ptr ptr(std::move(ptr_orig)); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/compile_test_implicit_const_base_to_derived3.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_unique_ptr ptr_orig( 5 | new test_object, test_deleter{42}); 6 | oup::observable_unique_ptr ptr(std::move(ptr_orig)); 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/compile_test_observer_assign_raw.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observer_ptr ptr; 5 | ptr = new test_object; 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/compile_test_observer_construct_raw.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observer_ptr ptr(new test_object); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/compile_test_sealed_assign_raw.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_sealed_ptr ptr; 5 | ptr = new test_object; 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/compile_test_sealed_construct_raw.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_sealed_ptr ptr(new test_object); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/compile_test_sealed_release.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_sealed_ptr ptr; 5 | ptr.release(); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/compile_test_sealed_reset.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | int main() { 4 | oup::observable_sealed_ptr ptr; 5 | ptr.reset(new test_object); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/memory_tracker.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | volatile void* allocations[max_allocations]; 8 | volatile void* allocations_array[max_allocations]; 9 | volatile std::size_t allocations_bytes[max_allocations]; 10 | volatile std::size_t num_allocations = 0u; 11 | volatile std::size_t size_allocations = 0u; 12 | volatile std::size_t double_delete = 0u; 13 | volatile bool memory_tracking = false; 14 | volatile bool force_next_allocation_failure = false; 15 | 16 | constexpr bool debug_alloc = false; 17 | constexpr bool scramble_alloc = true; 18 | 19 | void scramble(void* ptr, std::size_t size) { 20 | // Create a static random-like array, and use it to populate the allocated buffer. 21 | // clang-format off 22 | constexpr unsigned char random_bytes[] = { 23 | 177, 250, 8, 188, 247, 14, 164, 181, 116, 101, 13, 153, 2, 90, 92, 36, 24 | 240, 213, 38, 155, 223, 27, 100, 155, 182, 77, 106, 213, 219, 61, 36, 53, 25 | 244, 206, 197, 28, 39, 55, 228, 147, 217, 13, 146, 1, 216, 252, 59, 109, 26 | 143, 100, 101, 55, 204, 185, 40, 55, 197, 207, 187, 222, 13, 23, 177, 172, 27 | 165, 67, 252, 163, 89, 51, 19, 15, 107, 92, 103, 129, 65, 89, 189, 21, 28 | 206, 236, 130, 211, 148, 223, 104, 14, 54, 88, 54, 148, 227, 127, 234, 3, 29 | 125, 86, 184, 161, 109, 32, 124, 150, 54, 194, 56, 128, 7, 144, 139, 30, 30 | 226, 145, 4, 199, 11, 50, 241, 86, 72, 143, 215, 199, 0, 7, 124, 161 31 | }; 32 | // clang-format on 33 | 34 | unsigned char* bytes_ptr = static_cast(ptr); 35 | while (size > sizeof(random_bytes)) { 36 | std::copy(random_bytes, random_bytes + sizeof(random_bytes), bytes_ptr); 37 | size -= sizeof(random_bytes); 38 | } 39 | 40 | std::copy(random_bytes, random_bytes + size, bytes_ptr); 41 | } 42 | 43 | void* allocate(std::size_t size, bool array, std::align_val_t align) { 44 | if (memory_tracking && num_allocations == max_allocations) { 45 | if constexpr (debug_alloc) { 46 | std::printf("alloc %zu failed\n", size); 47 | } 48 | throw std::bad_alloc(); 49 | } 50 | 51 | if (force_next_allocation_failure) { 52 | if constexpr (debug_alloc) { 53 | std::printf("alloc %zu failed\n", size); 54 | } 55 | force_next_allocation_failure = false; 56 | throw std::bad_alloc(); 57 | } 58 | 59 | void* p = nullptr; 60 | if (align == std::align_val_t{0}) { 61 | p = std::malloc(size); 62 | } else { 63 | #if defined(OUP_COMPILER_MSVC) 64 | p = _aligned_malloc(size, static_cast(align)); 65 | #elif defined(OUP_COMPILER_EMSCRIPTEN) 66 | p = aligned_alloc(static_cast(align), size); 67 | #else 68 | p = std::aligned_alloc(static_cast(align), size); 69 | #endif 70 | } 71 | 72 | if (!p) { 73 | if constexpr (debug_alloc) { 74 | std::printf("alloc %zu failed\n", size); 75 | } 76 | throw std::bad_alloc(); 77 | } 78 | 79 | if constexpr (debug_alloc) { 80 | std::printf("alloc %zu -> %p\n", size, p); 81 | } 82 | 83 | if constexpr (scramble_alloc) { 84 | scramble(p, size); 85 | } 86 | 87 | if (memory_tracking) { 88 | if (array) { 89 | allocations_array[num_allocations] = p; 90 | } else { 91 | allocations[num_allocations] = p; 92 | } 93 | 94 | allocations_bytes[num_allocations] = size; 95 | 96 | num_allocations = num_allocations + 1u; 97 | size_allocations = size_allocations + size; 98 | } 99 | 100 | return p; 101 | } 102 | 103 | void deallocate(void* p, bool array, std::align_val_t align [[maybe_unused]]) { 104 | if (p == nullptr) { 105 | return; 106 | } 107 | 108 | if constexpr (debug_alloc) { 109 | std::printf("dealloc %p\n", p); 110 | } 111 | 112 | if (memory_tracking) { 113 | bool found = false; 114 | volatile void** allocations_type = array ? allocations_array : allocations; 115 | for (std::size_t i = 0; i < num_allocations; ++i) { 116 | if (allocations_type[i] == p) { 117 | std::swap(allocations_type[i], allocations_type[num_allocations - 1]); 118 | std::swap(allocations_bytes[i], allocations_bytes[num_allocations - 1]); 119 | num_allocations = num_allocations - 1u; 120 | size_allocations = size_allocations - allocations_bytes[num_allocations - 1]; 121 | found = true; 122 | break; 123 | } 124 | } 125 | 126 | if (!found) { 127 | double_delete = double_delete + 1u; 128 | } 129 | } 130 | 131 | if (align == std::align_val_t{0}) { 132 | std::free(p); 133 | } else { 134 | #if defined(OUP_COMPILER_MSVC) 135 | _aligned_free(p); 136 | #else 137 | std::free(p); 138 | #endif 139 | } 140 | } 141 | 142 | void* operator new(std::size_t size) { 143 | return allocate(size, false, std::align_val_t{0}); 144 | } 145 | 146 | void* operator new[](std::size_t size) { 147 | return allocate(size, true, std::align_val_t{0}); 148 | } 149 | 150 | void* operator new(std::size_t size, std::align_val_t al) { 151 | return allocate(size, false, al); 152 | } 153 | 154 | void* operator new[](std::size_t size, std::align_val_t al) { 155 | return allocate(size, true, al); 156 | } 157 | 158 | void operator delete(void* p) noexcept { 159 | deallocate(p, false, std::align_val_t{0}); 160 | } 161 | 162 | void operator delete[](void* p) noexcept { 163 | deallocate(p, true, std::align_val_t{0}); 164 | } 165 | 166 | void operator delete(void* p, std::size_t) noexcept { 167 | deallocate(p, false, std::align_val_t{0}); 168 | } 169 | 170 | void operator delete[](void* p, std::size_t) noexcept { 171 | deallocate(p, true, std::align_val_t{0}); 172 | } 173 | 174 | void operator delete(void* p, std::align_val_t al) noexcept { 175 | deallocate(p, false, al); 176 | } 177 | 178 | void operator delete[](void* p, std::align_val_t al) noexcept { 179 | deallocate(p, true, al); 180 | } 181 | 182 | memory_tracker::memory_tracker() noexcept : 183 | initial_allocations(::num_allocations), initial_double_delete(::double_delete) { 184 | ::memory_tracking = true; 185 | } 186 | 187 | memory_tracker::~memory_tracker() noexcept { 188 | ::memory_tracking = false; 189 | } 190 | 191 | std::size_t memory_tracker::allocated() const volatile { 192 | return ::num_allocations - initial_allocations; 193 | } 194 | 195 | std::size_t memory_tracker::double_delete() const volatile { 196 | return ::double_delete - initial_double_delete; 197 | } 198 | 199 | fail_next_allocation::fail_next_allocation() noexcept { 200 | force_next_allocation_failure = true; 201 | } 202 | 203 | fail_next_allocation::~fail_next_allocation() noexcept { 204 | force_next_allocation_failure = false; 205 | } 206 | -------------------------------------------------------------------------------- /tests/memory_tracker.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Allocation tracker, to catch memory leaks and double delete 5 | constexpr std::size_t max_allocations = 20'000; 6 | extern volatile void* allocations[max_allocations]; 7 | extern volatile void* allocations_array[max_allocations]; 8 | extern volatile std::size_t allocations_bytes[max_allocations]; 9 | extern volatile std::size_t num_allocations; 10 | extern volatile std::size_t size_allocations; 11 | extern volatile std::size_t double_delete; 12 | extern volatile bool memory_tracking; 13 | extern volatile bool force_next_allocation_failure; 14 | 15 | void* operator new(std::size_t size); 16 | 17 | void* operator new[](std::size_t size); 18 | 19 | void* operator new(std::size_t size, std::align_val_t al); 20 | 21 | void* operator new[](std::size_t size, std::align_val_t al); 22 | 23 | void operator delete(void* p) noexcept; 24 | 25 | void operator delete[](void* p) noexcept; 26 | 27 | void operator delete(void* p, std::size_t size) noexcept; 28 | 29 | void operator delete[](void* p, std::size_t size) noexcept; 30 | 31 | void operator delete(void* p, std::align_val_t al) noexcept; 32 | 33 | void operator delete[](void* p, std::align_val_t al) noexcept; 34 | 35 | struct memory_tracker { 36 | std::size_t initial_allocations; 37 | std::size_t initial_double_delete; 38 | 39 | memory_tracker() noexcept; 40 | ~memory_tracker() noexcept; 41 | 42 | std::size_t allocated() const volatile; 43 | std::size_t double_delete() const volatile; 44 | }; 45 | 46 | struct fail_next_allocation { 47 | fail_next_allocation() noexcept; 48 | ~fail_next_allocation() noexcept; 49 | }; 50 | -------------------------------------------------------------------------------- /tests/runtime_tests_lifetime.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | #include 5 | #include 6 | 7 | TEMPLATE_LIST_TEST_CASE("observer expiring scope", "[lifetime][owner][observer]", owner_types) { 8 | volatile memory_tracker mem_track; 9 | 10 | { 11 | observer_ptr optr; 12 | { 13 | TestType ptr = make_pointer_deleter_1(); 14 | auto* ptr_raw = ptr.get(); 15 | optr = ptr; 16 | 17 | CHECK(optr.get() == ptr_raw); 18 | CHECK(!optr.expired()); 19 | } 20 | 21 | CHECK(optr.get() == nullptr); 22 | CHECK(optr.expired()); 23 | CHECK_INSTANCES(0, 0); 24 | } 25 | 26 | CHECK_NO_LEAKS; 27 | } 28 | 29 | TEMPLATE_LIST_TEST_CASE( 30 | "observer not expiring when owner moved", "[lifetime][owner][observer]", owner_types) { 31 | volatile memory_tracker mem_track; 32 | 33 | { 34 | TestType outer_ptr; 35 | { 36 | observer_ptr optr; 37 | { 38 | TestType ptr = make_pointer_deleter_1(); 39 | auto* ptr_raw = ptr.get(); 40 | optr = ptr; 41 | 42 | CHECK(optr.get() == ptr_raw); 43 | CHECK(!optr.expired()); 44 | 45 | outer_ptr = std::move(ptr); 46 | 47 | CHECK(optr.get() == ptr_raw); 48 | CHECK(!optr.expired()); 49 | CHECK_INSTANCES(1, 2); 50 | } 51 | 52 | CHECK(optr.get() == outer_ptr.get()); 53 | CHECK(!optr.expired()); 54 | CHECK_INSTANCES(1, 1); 55 | } 56 | 57 | CHECK_INSTANCES(1, 1); 58 | } 59 | 60 | CHECK_NO_LEAKS; 61 | } 62 | 63 | TEMPLATE_LIST_TEST_CASE("observer expiring reset", "[lifetime][owner][observer]", owner_types) { 64 | volatile memory_tracker mem_track; 65 | 66 | { 67 | observer_ptr optr; 68 | TestType ptr = make_pointer_deleter_1(); 69 | auto* ptr_raw = ptr.get(); 70 | 71 | optr = ptr; 72 | 73 | CHECK(optr.get() == ptr_raw); 74 | CHECK(!optr.expired()); 75 | 76 | ptr.reset(); 77 | 78 | CHECK(optr.get() == nullptr); 79 | CHECK(optr.expired()); 80 | CHECK_INSTANCES(0, 1); 81 | } 82 | 83 | CHECK_NO_LEAKS; 84 | } 85 | 86 | TEMPLATE_LIST_TEST_CASE( 87 | "release valid owner with observer", "[lifetime][release][owner][observer]", owner_types) { 88 | if constexpr (!is_sealed) { 89 | volatile memory_tracker mem_track; 90 | 91 | { 92 | observer_ptr optr; 93 | { 94 | TestType ptr = make_pointer_deleter_1(); 95 | auto* ptr_raw = ptr.get(); 96 | 97 | optr = ptr; 98 | 99 | CHECK(optr.get() == ptr_raw); 100 | CHECK(!optr.expired()); 101 | 102 | auto* ptr_released = ptr.release(); 103 | 104 | CHECK(ptr_released == ptr_raw); 105 | CHECK(ptr.get() == nullptr); 106 | if constexpr (has_eoft) { 107 | CHECK(optr.get() != nullptr); 108 | CHECK(!optr.expired()); 109 | } else { 110 | CHECK(optr.get() == nullptr); 111 | CHECK(optr.expired()); 112 | } 113 | if constexpr (has_stateful_deleter) { 114 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 115 | } 116 | CHECK_INSTANCES(1, 1); 117 | 118 | delete ptr_released; 119 | } 120 | 121 | CHECK(optr.get() == nullptr); 122 | CHECK(optr.expired()); 123 | CHECK_INSTANCES(0, 0); 124 | } 125 | 126 | CHECK_NO_LEAKS; 127 | } 128 | } 129 | 130 | TEMPLATE_LIST_TEST_CASE( 131 | "release valid owner with observer subobject", 132 | "[lifetime][release][owner][observer]", 133 | owner_types) { 134 | if constexpr (!is_sealed) { 135 | volatile memory_tracker mem_track; 136 | 137 | { 138 | state_observer_ptr optr; 139 | { 140 | TestType ptr = make_pointer_deleter_1(); 141 | auto* ptr_raw = ptr.get(); 142 | 143 | optr = state_observer_ptr{ptr, &ptr->state_}; 144 | CHECK(optr.get() == &ptr_raw->state_); 145 | CHECK(!optr.expired()); 146 | 147 | auto* ptr_released = ptr.release(); 148 | CHECK(ptr_released == ptr_raw); 149 | CHECK(ptr.get() == nullptr); 150 | if constexpr (has_eoft) { 151 | CHECK(optr.get() != nullptr); 152 | CHECK(!optr.expired()); 153 | } else { 154 | CHECK(optr.get() == nullptr); 155 | CHECK(optr.expired()); 156 | } 157 | if constexpr (has_stateful_deleter) { 158 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 159 | } 160 | CHECK_INSTANCES(1, 1); 161 | 162 | delete ptr_released; 163 | } 164 | 165 | CHECK(optr.get() == nullptr); 166 | CHECK(optr.expired()); 167 | CHECK_INSTANCES(0, 0); 168 | } 169 | 170 | CHECK_NO_LEAKS; 171 | } 172 | } 173 | 174 | TEMPLATE_LIST_TEST_CASE( 175 | "observer get and raw get", "[lifetime][get][raw_get][owner][observer]", owner_types) { 176 | volatile memory_tracker mem_track; 177 | 178 | { 179 | observer_ptr optr; 180 | 181 | CHECK(optr.raw_get() == nullptr); 182 | CHECK(optr.get() == nullptr); 183 | 184 | TestType ptr = make_pointer_deleter_1(); 185 | optr = ptr; 186 | 187 | CHECK(optr.raw_get() == ptr.get()); 188 | CHECK(optr.get() == ptr.get()); 189 | 190 | get_object* raw_ptr = ptr.get(); 191 | ptr.reset(); 192 | 193 | CHECK(optr.raw_get() == raw_ptr); 194 | CHECK(optr.get() == nullptr); 195 | } 196 | 197 | CHECK_NO_LEAKS; 198 | } 199 | 200 | TEMPLATE_LIST_TEST_CASE( 201 | "object owning observer pointer to itself", 202 | "[lifetime][cycles][owner][observer]", 203 | owner_types) { 204 | if constexpr (is_cyclic) { 205 | volatile memory_tracker mem_track; 206 | 207 | { 208 | TestType ptr = make_pointer_deleter_1(); 209 | ptr->obs = ptr; 210 | 211 | CHECK_INSTANCES(1, 1); 212 | } 213 | 214 | CHECK_NO_LEAKS; 215 | } 216 | } 217 | 218 | TEMPLATE_LIST_TEST_CASE( 219 | "object owning observer pointer to other", "[lifetime][cycles][owner][observer]", owner_types) { 220 | if constexpr (is_cyclic) { 221 | volatile memory_tracker mem_track; 222 | 223 | { 224 | TestType ptr1 = make_pointer_deleter_1(); 225 | TestType ptr2 = make_pointer_deleter_2(); 226 | ptr1->obs = ptr2; 227 | ptr2->obs = ptr1; 228 | 229 | CHECK_INSTANCES(2, 2); 230 | } 231 | 232 | CHECK_NO_LEAKS; 233 | } 234 | } 235 | 236 | TEMPLATE_LIST_TEST_CASE( 237 | "object owning observer pointer open chain", 238 | "[lifetime][cycles][owner][observer]", 239 | owner_types) { 240 | if constexpr (is_cyclic) { 241 | volatile memory_tracker mem_track; 242 | 243 | { 244 | TestType ptr1 = make_pointer_deleter_1(); 245 | TestType ptr2 = make_pointer_deleter_2(); 246 | TestType ptr3 = make_pointer_deleter_1(); 247 | ptr1->obs = ptr2; 248 | ptr2->obs = ptr3; 249 | 250 | CHECK_INSTANCES(3, 3); 251 | } 252 | 253 | CHECK_NO_LEAKS; 254 | } 255 | } 256 | 257 | TEMPLATE_LIST_TEST_CASE( 258 | "object owning observer pointer open chain reversed", 259 | "[lifetime][cycles][owner][observer]", 260 | owner_types) { 261 | if constexpr (is_cyclic) { 262 | volatile memory_tracker mem_track; 263 | 264 | { 265 | TestType ptr1 = make_pointer_deleter_1(); 266 | TestType ptr2 = make_pointer_deleter_2(); 267 | TestType ptr3 = make_pointer_deleter_1(); 268 | ptr3->obs = ptr2; 269 | ptr2->obs = ptr1; 270 | 271 | CHECK_INSTANCES(3, 3); 272 | } 273 | 274 | CHECK_NO_LEAKS; 275 | } 276 | } 277 | 278 | TEMPLATE_LIST_TEST_CASE( 279 | "object owning observer pointer closed chain interleaved", 280 | "[lifetime][cycles][owner][observer]", 281 | owner_types) { 282 | if constexpr (is_cyclic) { 283 | volatile memory_tracker mem_track; 284 | 285 | { 286 | TestType ptr1 = make_pointer_deleter_1(); 287 | TestType ptr2 = make_pointer_deleter_2(); 288 | TestType ptr3 = make_pointer_deleter_1(); 289 | TestType ptr4 = make_pointer_deleter_2(); 290 | ptr1->obs = ptr2; 291 | ptr2->obs = ptr4; 292 | ptr3->obs = ptr1; 293 | ptr4->obs = ptr3; 294 | 295 | CHECK_INSTANCES(4, 4); 296 | } 297 | 298 | CHECK_NO_LEAKS; 299 | } 300 | } 301 | 302 | TEMPLATE_LIST_TEST_CASE("pointers in vector", "[lifetime][array][owner][observer]", owner_types) { 303 | volatile memory_tracker mem_track; 304 | 305 | { 306 | std::vector vec_own; 307 | std::vector> vec_obs; 308 | 309 | vec_own.resize(100); 310 | 311 | CHECK(std::all_of(vec_own.begin(), vec_own.end(), [](const auto& p) { 312 | return p == nullptr; 313 | }) == true); 314 | 315 | std::generate( 316 | vec_own.begin(), vec_own.end(), []() { return make_pointer_deleter_1(); }); 317 | 318 | CHECK(std::none_of(vec_own.begin(), vec_own.end(), [](const auto& p) { 319 | return p == nullptr; 320 | }) == true); 321 | 322 | vec_obs.resize(100); 323 | 324 | CHECK(std::all_of(vec_obs.begin(), vec_obs.end(), [](const auto& p) { 325 | return p == nullptr; 326 | }) == true); 327 | 328 | std::copy(vec_own.begin(), vec_own.end(), vec_obs.begin()); 329 | 330 | CHECK(std::none_of(vec_own.begin(), vec_own.end(), [](const auto& p) { 331 | return p == nullptr; 332 | }) == true); 333 | 334 | std::vector vec_own_new = std::move(vec_own); 335 | 336 | CHECK(std::none_of(vec_own.begin(), vec_own.end(), [](const auto& p) { 337 | return p == nullptr; 338 | }) == true); 339 | 340 | vec_own_new.clear(); 341 | 342 | CHECK(std::all_of(vec_obs.begin(), vec_obs.end(), [](const auto& p) { 343 | return p == nullptr; 344 | }) == true); 345 | } 346 | 347 | CHECK_NO_LEAKS; 348 | } 349 | -------------------------------------------------------------------------------- /tests/runtime_tests_make_observable.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE("make observable", "[make_observable][owner]", owner_types) { 5 | if constexpr (can_use_make_observable) { 6 | volatile memory_tracker mem_track; 7 | 8 | { 9 | TestType ptr = oup::make_observable, get_policy>(); 10 | 11 | if constexpr (is_sealed) { 12 | CHECK_MAX_ALLOC(1u); 13 | } else { 14 | CHECK_MAX_ALLOC(2u); 15 | } 16 | CHECK(ptr.get() != nullptr); 17 | CHECK(ptr->state_ == test_object::state::default_init); 18 | if constexpr (has_stateful_deleter) { 19 | CHECK(ptr.get_deleter().state_ == test_deleter::state::default_init); 20 | } 21 | CHECK_INSTANCES(1, 1); 22 | } 23 | 24 | CHECK_NO_LEAKS; 25 | } 26 | } 27 | 28 | TEMPLATE_LIST_TEST_CASE("make observable with arguments", "[make_observable][owner]", owner_types) { 29 | if constexpr (can_use_make_observable) { 30 | volatile memory_tracker mem_track; 31 | 32 | { 33 | TestType ptr = oup::make_observable, get_policy>( 34 | test_object::state::special_init); 35 | 36 | if constexpr (is_sealed) { 37 | CHECK_MAX_ALLOC(1u); 38 | } else { 39 | CHECK_MAX_ALLOC(2u); 40 | } 41 | CHECK(ptr.get() != nullptr); 42 | CHECK(ptr->state_ == test_object::state::special_init); 43 | if constexpr (has_stateful_deleter) { 44 | CHECK(ptr.get_deleter().state_ == test_deleter::state::default_init); 45 | } 46 | CHECK_INSTANCES(1, 1); 47 | } 48 | 49 | CHECK_NO_LEAKS; 50 | } 51 | } 52 | 53 | TEMPLATE_LIST_TEST_CASE( 54 | "make observable throw in constructor", "[make_observable][owner]", owner_types) { 55 | if constexpr (can_use_make_observable) { 56 | volatile memory_tracker mem_track; 57 | 58 | next_test_object_constructor_throws = true; 59 | REQUIRE_THROWS_AS( 60 | (oup::make_observable, get_policy>()), 61 | throw_constructor); 62 | 63 | CHECK_NO_LEAKS; 64 | } 65 | } 66 | 67 | TEMPLATE_LIST_TEST_CASE("make observable bad alloc", "[make_observable][owner]", owner_types) { 68 | if constexpr (can_use_make_observable) { 69 | volatile memory_tracker mem_track; 70 | 71 | force_next_allocation_failure = true; 72 | REQUIRE_THROWS_AS( 73 | (oup::make_observable, get_policy>()), std::bad_alloc); 74 | 75 | CHECK_NO_LEAKS; 76 | } 77 | } 78 | 79 | TEST_CASE("make observable unique", "[make_observable][owner]") { 80 | using TestType = oup::observable_unique_ptr; 81 | volatile memory_tracker mem_track; 82 | 83 | { 84 | TestType ptr = oup::make_observable_unique(); 85 | 86 | CHECK_MAX_ALLOC(2u); 87 | CHECK(ptr.get() != nullptr); 88 | CHECK(ptr->state_ == test_object::state::default_init); 89 | CHECK_INSTANCES(1, 1); 90 | } 91 | 92 | CHECK_NO_LEAKS; 93 | } 94 | 95 | TEST_CASE("make observable sealed", "[make_observable][owner]") { 96 | using TestType = oup::observable_sealed_ptr; 97 | volatile memory_tracker mem_track; 98 | 99 | { 100 | TestType ptr = oup::make_observable_sealed(); 101 | 102 | CHECK_MAX_ALLOC(1u); 103 | CHECK(ptr.get() != nullptr); 104 | CHECK(ptr->state_ == test_object::state::default_init); 105 | CHECK_INSTANCES(1, 1); 106 | } 107 | 108 | CHECK_NO_LEAKS; 109 | } 110 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_assignment_copy.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE( 5 | "observer copy assignment operator valid to empty", "[assignment][observer]", owner_types) { 6 | volatile memory_tracker mem_track; 7 | 8 | { 9 | TestType ptr_orig = make_pointer_deleter_1(); 10 | observer_ptr optr_orig{ptr_orig}; 11 | { 12 | observer_ptr optr; 13 | optr = optr_orig; 14 | 15 | CHECK(optr.get() == ptr_orig.get()); 16 | CHECK(optr_orig.get() == ptr_orig.get()); 17 | CHECK(optr.expired() == false); 18 | CHECK(optr_orig.expired() == false); 19 | CHECK_INSTANCES(1, 1); 20 | } 21 | 22 | CHECK_INSTANCES(1, 1); 23 | } 24 | 25 | CHECK_NO_LEAKS; 26 | } 27 | 28 | TEMPLATE_LIST_TEST_CASE( 29 | "observer copy assignment operator empty to valid", "[assignment][observer]", owner_types) { 30 | volatile memory_tracker mem_track; 31 | 32 | { 33 | observer_ptr optr_orig; 34 | { 35 | TestType ptr = make_pointer_deleter_1(); 36 | observer_ptr optr{ptr}; 37 | optr = optr_orig; 38 | 39 | CHECK(optr.get() == nullptr); 40 | CHECK(optr_orig.get() == nullptr); 41 | CHECK(optr.expired() == true); 42 | CHECK(optr_orig.expired() == true); 43 | CHECK_INSTANCES(1, 1); 44 | } 45 | 46 | CHECK_INSTANCES(0, 0); 47 | } 48 | 49 | CHECK_NO_LEAKS; 50 | } 51 | 52 | TEMPLATE_LIST_TEST_CASE( 53 | "observer copy assignment operator empty to empty", "[assignment][observer]", owner_types) { 54 | volatile memory_tracker mem_track; 55 | 56 | { 57 | observer_ptr optr_orig; 58 | { 59 | observer_ptr optr; 60 | optr = optr_orig; 61 | 62 | CHECK(optr.get() == nullptr); 63 | CHECK(optr_orig.get() == nullptr); 64 | CHECK(optr.expired() == true); 65 | CHECK(optr_orig.expired() == true); 66 | CHECK_INSTANCES(0, 0); 67 | } 68 | 69 | CHECK_INSTANCES(0, 0); 70 | } 71 | 72 | CHECK_NO_LEAKS; 73 | } 74 | 75 | TEMPLATE_LIST_TEST_CASE( 76 | "observer copy assignment operator valid to valid", "[assignment][observer]", owner_types) { 77 | volatile memory_tracker mem_track; 78 | 79 | { 80 | TestType ptr_orig = make_pointer_deleter_1(); 81 | observer_ptr optr_orig{ptr_orig}; 82 | { 83 | TestType ptr = make_pointer_deleter_1(); 84 | observer_ptr optr{ptr}; 85 | optr = optr_orig; 86 | 87 | CHECK(optr.get() == ptr_orig.get()); 88 | CHECK(optr_orig.get() == ptr_orig.get()); 89 | CHECK(optr.expired() == false); 90 | CHECK(optr_orig.expired() == false); 91 | CHECK_INSTANCES(2, 2); 92 | } 93 | 94 | CHECK_INSTANCES(1, 1); 95 | } 96 | 97 | CHECK_NO_LEAKS; 98 | } 99 | 100 | TEMPLATE_LIST_TEST_CASE( 101 | "observer copy assignment converting operator valid to empty", 102 | "[assignment][observer]", 103 | owner_types) { 104 | volatile memory_tracker mem_track; 105 | 106 | if constexpr (has_base) { 107 | { 108 | TestType ptr_orig = make_pointer_deleter_1(); 109 | observer_ptr optr_orig{ptr_orig}; 110 | { 111 | base_observer_ptr optr; 112 | optr = optr_orig; 113 | 114 | CHECK(optr.get() == ptr_orig.get()); 115 | CHECK(optr_orig.get() == ptr_orig.get()); 116 | CHECK(optr.expired() == false); 117 | CHECK(optr_orig.expired() == false); 118 | CHECK_INSTANCES(1, 1); 119 | } 120 | 121 | CHECK_INSTANCES(1, 1); 122 | } 123 | 124 | CHECK_NO_LEAKS; 125 | } 126 | } 127 | 128 | TEMPLATE_LIST_TEST_CASE( 129 | "observer copy assignment converting operator empty to valid", 130 | "[assignment][observer]", 131 | owner_types) { 132 | volatile memory_tracker mem_track; 133 | 134 | if constexpr (has_base) { 135 | { 136 | observer_ptr optr_orig; 137 | { 138 | TestType ptr = make_pointer_deleter_1(); 139 | base_observer_ptr optr{ptr}; 140 | optr = optr_orig; 141 | 142 | CHECK(optr.get() == nullptr); 143 | CHECK(optr_orig.get() == nullptr); 144 | CHECK(optr.expired() == true); 145 | CHECK(optr_orig.expired() == true); 146 | CHECK_INSTANCES(1, 1); 147 | } 148 | 149 | CHECK_INSTANCES(0, 0); 150 | } 151 | 152 | CHECK_NO_LEAKS; 153 | } 154 | } 155 | 156 | TEMPLATE_LIST_TEST_CASE( 157 | "observer copy assignment converting operator empty to empty", 158 | "[assignment][observer]", 159 | owner_types) { 160 | volatile memory_tracker mem_track; 161 | 162 | if constexpr (has_base) { 163 | { 164 | observer_ptr optr_orig; 165 | { 166 | base_observer_ptr optr; 167 | optr = optr_orig; 168 | 169 | CHECK(optr.get() == nullptr); 170 | CHECK(optr_orig.get() == nullptr); 171 | CHECK(optr.expired() == true); 172 | CHECK(optr_orig.expired() == true); 173 | CHECK_INSTANCES(0, 0); 174 | } 175 | 176 | CHECK_INSTANCES(0, 0); 177 | } 178 | 179 | CHECK_NO_LEAKS; 180 | } 181 | } 182 | 183 | TEMPLATE_LIST_TEST_CASE( 184 | "observer copy assignment converting operator valid to valid", 185 | "[assignment][observer]", 186 | owner_types) { 187 | volatile memory_tracker mem_track; 188 | 189 | if constexpr (has_base) { 190 | { 191 | TestType ptr_orig = make_pointer_deleter_1(); 192 | observer_ptr optr_orig{ptr_orig}; 193 | { 194 | TestType ptr = make_pointer_deleter_1(); 195 | base_observer_ptr optr{ptr}; 196 | optr = optr_orig; 197 | 198 | CHECK(optr.get() == ptr_orig.get()); 199 | CHECK(optr_orig.get() == ptr_orig.get()); 200 | CHECK(optr.expired() == false); 201 | CHECK(optr_orig.expired() == false); 202 | CHECK_INSTANCES(2, 2); 203 | } 204 | 205 | CHECK_INSTANCES(1, 1); 206 | } 207 | 208 | CHECK_NO_LEAKS; 209 | } 210 | } 211 | 212 | TEMPLATE_LIST_TEST_CASE( 213 | "observer copy assignment operator self to self valid", "[assignment][observer]", owner_types) { 214 | volatile memory_tracker mem_track; 215 | 216 | { 217 | TestType ptr = make_pointer_deleter_1(); 218 | observer_ptr optr{ptr}; 219 | SNITCH_WARNING_PUSH; 220 | SNITCH_WARNING_DISABLE_SELF_ASSIGN; 221 | optr = optr; 222 | SNITCH_WARNING_POP; 223 | 224 | CHECK(optr.get() == ptr.get()); 225 | CHECK(optr.expired() == false); 226 | CHECK_INSTANCES(1, 1); 227 | } 228 | 229 | CHECK_NO_LEAKS; 230 | } 231 | 232 | TEMPLATE_LIST_TEST_CASE( 233 | "observer copy assignment operator self to self empty", "[assignment][observer]", owner_types) { 234 | volatile memory_tracker mem_track; 235 | 236 | { 237 | observer_ptr optr; 238 | SNITCH_WARNING_PUSH; 239 | SNITCH_WARNING_DISABLE_SELF_ASSIGN; 240 | optr = optr; 241 | SNITCH_WARNING_POP; 242 | 243 | CHECK(optr.get() == nullptr); 244 | CHECK(optr.expired() == true); 245 | CHECK_INSTANCES(0, 0); 246 | } 247 | 248 | CHECK_NO_LEAKS; 249 | } 250 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_assignment_from_owner.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE( 5 | "observer from owner assignment operator valid to empty", 6 | "[assignment][observer]", 7 | owner_types) { 8 | memory_tracker mem_track; 9 | 10 | { 11 | TestType ptr_orig = make_pointer_deleter_1(); 12 | { 13 | observer_ptr optr; 14 | optr = ptr_orig; 15 | 16 | CHECK(optr.get() == ptr_orig.get()); 17 | CHECK(optr.expired() == false); 18 | CHECK_INSTANCES(1, 1); 19 | } 20 | 21 | CHECK_INSTANCES(1, 1); 22 | } 23 | 24 | CHECK_NO_LEAKS; 25 | } 26 | 27 | TEMPLATE_LIST_TEST_CASE( 28 | "observer from owner assignment operator empty to valid", 29 | "[assignment][observer]", 30 | owner_types) { 31 | memory_tracker mem_track; 32 | 33 | { 34 | TestType ptr_orig; 35 | { 36 | TestType ptr = make_pointer_deleter_1(); 37 | observer_ptr optr{ptr}; 38 | optr = ptr_orig; 39 | 40 | CHECK(optr.get() == nullptr); 41 | CHECK(optr.expired() == true); 42 | CHECK(ptr_orig.get() == nullptr); 43 | CHECK_INSTANCES(1, 2); 44 | } 45 | 46 | CHECK_INSTANCES(0, 1); 47 | } 48 | 49 | CHECK_NO_LEAKS; 50 | } 51 | 52 | TEMPLATE_LIST_TEST_CASE( 53 | "observer from owner assignment operator empty to empty", 54 | "[assignment][observer]", 55 | owner_types) { 56 | memory_tracker mem_track; 57 | 58 | { 59 | TestType ptr_orig; 60 | { 61 | observer_ptr optr; 62 | optr = ptr_orig; 63 | 64 | CHECK(optr.get() == nullptr); 65 | CHECK(optr.expired() == true); 66 | CHECK(ptr_orig.get() == nullptr); 67 | CHECK_INSTANCES(0, 1); 68 | } 69 | 70 | CHECK_INSTANCES(0, 1); 71 | } 72 | 73 | CHECK_NO_LEAKS; 74 | } 75 | 76 | TEMPLATE_LIST_TEST_CASE( 77 | "observer from owner assignment operator valid to valid", 78 | "[assignment][observer]", 79 | owner_types) { 80 | memory_tracker mem_track; 81 | 82 | { 83 | TestType ptr_orig = make_pointer_deleter_1(); 84 | get_object* raw_ptr_orig = ptr_orig.get(); 85 | { 86 | TestType ptr = make_pointer_deleter_1(); 87 | get_object* raw_ptr = ptr.get(); 88 | observer_ptr optr{ptr}; 89 | optr = ptr_orig; 90 | 91 | CHECK(optr.get() == ptr_orig.get()); 92 | CHECK(optr.expired() == false); 93 | CHECK(ptr_orig.get() == raw_ptr_orig); 94 | CHECK(ptr.get() == raw_ptr); 95 | CHECK_INSTANCES(2, 2); 96 | } 97 | 98 | CHECK_INSTANCES(1, 1); 99 | } 100 | 101 | CHECK_NO_LEAKS; 102 | } 103 | 104 | TEMPLATE_LIST_TEST_CASE( 105 | "observer from owner assignment converting operator valid to empty", 106 | "[assignment][observer]", 107 | owner_types) { 108 | memory_tracker mem_track; 109 | 110 | if constexpr (has_base) { 111 | { 112 | TestType ptr_orig = make_pointer_deleter_1(); 113 | { 114 | base_observer_ptr optr; 115 | optr = ptr_orig; 116 | 117 | CHECK(optr.get() == ptr_orig.get()); 118 | CHECK(optr.expired() == false); 119 | CHECK_INSTANCES(1, 1); 120 | } 121 | 122 | CHECK_INSTANCES(1, 1); 123 | } 124 | 125 | CHECK_NO_LEAKS; 126 | } 127 | } 128 | 129 | TEMPLATE_LIST_TEST_CASE( 130 | "observer from owner assignment converting operator empty to valid", 131 | "[assignment][observer]", 132 | owner_types) { 133 | memory_tracker mem_track; 134 | 135 | if constexpr (has_base) { 136 | { 137 | TestType ptr_orig; 138 | { 139 | TestType ptr = make_pointer_deleter_1(); 140 | base_observer_ptr optr{ptr}; 141 | optr = ptr_orig; 142 | 143 | CHECK(optr.get() == nullptr); 144 | CHECK(optr.expired() == true); 145 | CHECK(ptr_orig.get() == nullptr); 146 | CHECK_INSTANCES(1, 2); 147 | } 148 | 149 | CHECK_INSTANCES(0, 1); 150 | } 151 | 152 | CHECK_NO_LEAKS; 153 | } 154 | } 155 | 156 | TEMPLATE_LIST_TEST_CASE( 157 | "observer from owner assignment converting operator empty to empty", 158 | "[assignment][observer]", 159 | owner_types) { 160 | memory_tracker mem_track; 161 | 162 | if constexpr (has_base) { 163 | { 164 | TestType ptr_orig; 165 | { 166 | base_observer_ptr optr; 167 | optr = ptr_orig; 168 | 169 | CHECK(optr.get() == nullptr); 170 | CHECK(optr.expired() == true); 171 | CHECK(ptr_orig.get() == nullptr); 172 | CHECK_INSTANCES(0, 1); 173 | } 174 | 175 | CHECK_INSTANCES(0, 1); 176 | } 177 | 178 | CHECK_NO_LEAKS; 179 | } 180 | } 181 | 182 | TEMPLATE_LIST_TEST_CASE( 183 | "observer from owner assignment converting operator valid to valid", 184 | "[assignment][observer]", 185 | owner_types) { 186 | memory_tracker mem_track; 187 | 188 | if constexpr (has_base) { 189 | { 190 | TestType ptr_orig = make_pointer_deleter_1(); 191 | get_object* raw_ptr_orig = ptr_orig.get(); 192 | { 193 | TestType ptr = make_pointer_deleter_1(); 194 | get_object* raw_ptr = ptr.get(); 195 | base_observer_ptr optr{ptr}; 196 | optr = ptr_orig; 197 | 198 | CHECK(optr.get() == ptr_orig.get()); 199 | CHECK(optr.expired() == false); 200 | CHECK(ptr_orig.get() == raw_ptr_orig); 201 | CHECK(ptr.get() == raw_ptr); 202 | CHECK_INSTANCES(2, 2); 203 | } 204 | 205 | CHECK_INSTANCES(1, 1); 206 | } 207 | 208 | CHECK_NO_LEAKS; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_assignment_move.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE( 5 | "observer move assignment operator valid to empty", "[assignment][observer]", owner_types) { 6 | volatile memory_tracker mem_track; 7 | 8 | { 9 | TestType ptr_orig = make_pointer_deleter_1(); 10 | observer_ptr optr_orig{ptr_orig}; 11 | { 12 | observer_ptr optr; 13 | optr = std::move(optr_orig); 14 | 15 | CHECK(optr.get() == ptr_orig.get()); 16 | CHECK(optr_orig.get() == nullptr); 17 | CHECK(optr.expired() == false); 18 | CHECK(optr_orig.expired() == true); 19 | CHECK_INSTANCES(1, 1); 20 | } 21 | 22 | CHECK_INSTANCES(1, 1); 23 | } 24 | 25 | CHECK_NO_LEAKS; 26 | } 27 | 28 | TEMPLATE_LIST_TEST_CASE( 29 | "observer move assignment operator empty to valid", "[assignment][observer]", owner_types) { 30 | volatile memory_tracker mem_track; 31 | 32 | { 33 | observer_ptr optr_orig; 34 | { 35 | TestType ptr = make_pointer_deleter_1(); 36 | observer_ptr optr{ptr}; 37 | optr = std::move(optr_orig); 38 | 39 | CHECK(optr.get() == nullptr); 40 | CHECK(optr_orig.get() == nullptr); 41 | CHECK(optr.expired() == true); 42 | CHECK(optr_orig.expired() == true); 43 | CHECK_INSTANCES(1, 1); 44 | } 45 | 46 | CHECK_INSTANCES(0, 0); 47 | } 48 | 49 | CHECK_NO_LEAKS; 50 | } 51 | 52 | TEMPLATE_LIST_TEST_CASE( 53 | "observer move assignment operator empty to empty", "[assignment][observer]", owner_types) { 54 | volatile memory_tracker mem_track; 55 | 56 | { 57 | observer_ptr optr_orig; 58 | { 59 | observer_ptr optr; 60 | optr = std::move(optr_orig); 61 | 62 | CHECK(optr.get() == nullptr); 63 | CHECK(optr_orig.get() == nullptr); 64 | CHECK(optr.expired() == true); 65 | CHECK(optr_orig.expired() == true); 66 | CHECK_INSTANCES(0, 0); 67 | } 68 | 69 | CHECK_INSTANCES(0, 0); 70 | } 71 | 72 | CHECK_NO_LEAKS; 73 | } 74 | 75 | TEMPLATE_LIST_TEST_CASE( 76 | "observer move assignment operator valid to valid", "[assignment][observer]", owner_types) { 77 | volatile memory_tracker mem_track; 78 | 79 | { 80 | TestType ptr_orig = make_pointer_deleter_1(); 81 | observer_ptr optr_orig{ptr_orig}; 82 | { 83 | TestType ptr = make_pointer_deleter_1(); 84 | observer_ptr optr{ptr}; 85 | optr = std::move(optr_orig); 86 | 87 | CHECK(optr.get() == ptr_orig.get()); 88 | CHECK(optr_orig.get() == nullptr); 89 | CHECK(optr.expired() == false); 90 | CHECK(optr_orig.expired() == true); 91 | CHECK_INSTANCES(2, 2); 92 | } 93 | 94 | CHECK_INSTANCES(1, 1); 95 | } 96 | 97 | CHECK_NO_LEAKS; 98 | } 99 | 100 | TEMPLATE_LIST_TEST_CASE( 101 | "observer move assignment converting operator valid to empty", 102 | "[assignment][observer]", 103 | owner_types) { 104 | volatile memory_tracker mem_track; 105 | 106 | if constexpr (has_base) { 107 | { 108 | TestType ptr_orig = make_pointer_deleter_1(); 109 | observer_ptr optr_orig{ptr_orig}; 110 | { 111 | base_observer_ptr optr; 112 | optr = std::move(optr_orig); 113 | 114 | CHECK(optr.get() == ptr_orig.get()); 115 | CHECK(optr_orig.get() == nullptr); 116 | CHECK(optr.expired() == false); 117 | CHECK(optr_orig.expired() == true); 118 | CHECK_INSTANCES(1, 1); 119 | } 120 | 121 | CHECK_INSTANCES(1, 1); 122 | } 123 | 124 | CHECK_NO_LEAKS; 125 | } 126 | } 127 | 128 | TEMPLATE_LIST_TEST_CASE( 129 | "observer move assignment converting operator empty to valid", 130 | "[assignment][observer]", 131 | owner_types) { 132 | volatile memory_tracker mem_track; 133 | 134 | if constexpr (has_base) { 135 | { 136 | observer_ptr optr_orig; 137 | { 138 | TestType ptr = make_pointer_deleter_1(); 139 | base_observer_ptr optr{ptr}; 140 | optr = std::move(optr_orig); 141 | 142 | CHECK(optr.get() == nullptr); 143 | CHECK(optr_orig.get() == nullptr); 144 | CHECK(optr.expired() == true); 145 | CHECK(optr_orig.expired() == true); 146 | CHECK_INSTANCES(1, 1); 147 | } 148 | 149 | CHECK_INSTANCES(0, 0); 150 | } 151 | 152 | CHECK_NO_LEAKS; 153 | } 154 | } 155 | 156 | TEMPLATE_LIST_TEST_CASE( 157 | "observer move assignment converting operator empty to empty", 158 | "[assignment][observer]", 159 | owner_types) { 160 | volatile memory_tracker mem_track; 161 | 162 | if constexpr (has_base) { 163 | { 164 | observer_ptr optr_orig; 165 | { 166 | base_observer_ptr optr; 167 | optr = std::move(optr_orig); 168 | 169 | CHECK(optr.get() == nullptr); 170 | CHECK(optr_orig.get() == nullptr); 171 | CHECK(optr.expired() == true); 172 | CHECK(optr_orig.expired() == true); 173 | CHECK_INSTANCES(0, 0); 174 | } 175 | 176 | CHECK_INSTANCES(0, 0); 177 | } 178 | 179 | CHECK_NO_LEAKS; 180 | } 181 | } 182 | 183 | TEMPLATE_LIST_TEST_CASE( 184 | "observer move assignment converting operator valid to valid", 185 | "[assignment][observer]", 186 | owner_types) { 187 | volatile memory_tracker mem_track; 188 | 189 | if constexpr (has_base) { 190 | { 191 | TestType ptr_orig = make_pointer_deleter_1(); 192 | observer_ptr optr_orig{ptr_orig}; 193 | { 194 | TestType ptr = make_pointer_deleter_1(); 195 | base_observer_ptr optr{ptr}; 196 | optr = std::move(optr_orig); 197 | 198 | CHECK(optr.get() == ptr_orig.get()); 199 | CHECK(optr_orig.get() == nullptr); 200 | CHECK(optr.expired() == false); 201 | CHECK(optr_orig.expired() == true); 202 | CHECK_INSTANCES(2, 2); 203 | } 204 | 205 | CHECK_INSTANCES(1, 1); 206 | } 207 | 208 | CHECK_NO_LEAKS; 209 | } 210 | } 211 | 212 | TEMPLATE_LIST_TEST_CASE( 213 | "observer move assignment operator self to self valid", "[assignment][observer]", owner_types) { 214 | volatile memory_tracker mem_track; 215 | 216 | { 217 | TestType ptr = make_pointer_deleter_1(); 218 | observer_ptr optr{ptr}; 219 | optr = std::move(optr); 220 | CHECK(optr.get() == nullptr); 221 | CHECK(optr.expired() == true); 222 | CHECK_INSTANCES(1, 1); 223 | } 224 | 225 | CHECK_NO_LEAKS; 226 | } 227 | 228 | TEMPLATE_LIST_TEST_CASE( 229 | "observer move assignment operator self to self empty", "[assignment][observer]", owner_types) { 230 | volatile memory_tracker mem_track; 231 | 232 | { 233 | observer_ptr optr; 234 | optr = std::move(optr); 235 | CHECK(optr.get() == nullptr); 236 | CHECK(optr.expired() == true); 237 | CHECK_INSTANCES(0, 0); 238 | } 239 | 240 | CHECK_NO_LEAKS; 241 | } 242 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_cast_copy.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE("observer static_cast copy from valid", "[cast][observer]", owner_types) { 5 | volatile memory_tracker mem_track; 6 | 7 | { 8 | auto run_test = [&]() { 9 | TestType ptr1 = make_pointer_deleter_1(); 10 | observer_ptr optr1 = ptr1; 11 | get_object* raw_ptr = ptr1.get(); 12 | auto optr2 = oup::static_pointer_cast(optr1); 13 | 14 | using return_type = std::remove_cv_t; 15 | 16 | CHECK(optr1.get() == raw_ptr); 17 | CHECK(optr2.get() == raw_ptr); 18 | CHECK(snitch::type_name == snitch::type_name); 19 | CHECK_INSTANCES(1, 1); 20 | }; 21 | 22 | run_test.template operator(), observer_ptr>(); 23 | run_test.template operator(), const_observer_ptr>(); 24 | if constexpr (has_base) { 25 | run_test.template operator(), base_observer_ptr>(); 26 | } 27 | } 28 | 29 | CHECK_NO_LEAKS; 30 | } 31 | 32 | TEMPLATE_LIST_TEST_CASE("observer static_cast copy from empty", "[cast][observer]", owner_types) { 33 | volatile memory_tracker mem_track; 34 | 35 | { 36 | auto run_test = [&]() { 37 | TestType ptr1 = make_empty_pointer_deleter_1(); 38 | observer_ptr optr1 = ptr1; 39 | auto optr2 = oup::static_pointer_cast(optr1); 40 | 41 | using return_type = std::remove_cv_t; 42 | 43 | CHECK(optr1.get() == nullptr); 44 | CHECK(optr2.get() == nullptr); 45 | CHECK(snitch::type_name == snitch::type_name); 46 | CHECK_INSTANCES(0, 1); 47 | }; 48 | 49 | run_test.template operator(), observer_ptr>(); 50 | run_test.template operator(), const_observer_ptr>(); 51 | if constexpr (has_base) { 52 | run_test.template operator(), base_observer_ptr>(); 53 | } 54 | } 55 | 56 | CHECK_NO_LEAKS; 57 | } 58 | 59 | TEMPLATE_LIST_TEST_CASE("observer const_cast copy from valid", "[cast][observer]", owner_types) { 60 | volatile memory_tracker mem_track; 61 | 62 | { 63 | auto run_test = [&]() { 64 | TestType ptr1 = make_pointer_deleter_1(); 65 | observer_ptr optr1 = ptr1; 66 | get_object* raw_ptr = ptr1.get(); 67 | auto optr2 = oup::const_pointer_cast(optr1); 68 | 69 | using return_type = std::remove_cv_t; 70 | 71 | CHECK(optr1.get() == raw_ptr); 72 | CHECK(optr2.get() == raw_ptr); 73 | CHECK(snitch::type_name == snitch::type_name); 74 | CHECK_INSTANCES(1, 1); 75 | }; 76 | 77 | run_test.template operator(), const_observer_ptr>(); 78 | run_test.template 79 | operator()>, mutable_observer_ptr>(); 80 | } 81 | 82 | CHECK_NO_LEAKS; 83 | } 84 | 85 | TEMPLATE_LIST_TEST_CASE("observer const_cast copy from empty", "[cast][observer]", owner_types) { 86 | volatile memory_tracker mem_track; 87 | 88 | { 89 | auto run_test = [&]() { 90 | TestType ptr1 = make_empty_pointer_deleter_1(); 91 | observer_ptr optr1 = ptr1; 92 | auto optr2 = oup::const_pointer_cast(optr1); 93 | 94 | using return_type = std::remove_cv_t; 95 | 96 | CHECK(optr1.get() == nullptr); 97 | CHECK(optr2.get() == nullptr); 98 | CHECK(snitch::type_name == snitch::type_name); 99 | CHECK_INSTANCES(0, 1); 100 | }; 101 | 102 | run_test.template operator(), const_observer_ptr>(); 103 | run_test.template 104 | operator()>, mutable_observer_ptr>(); 105 | } 106 | 107 | CHECK_NO_LEAKS; 108 | } 109 | 110 | TEMPLATE_LIST_TEST_CASE("observer dynamic_cast copy from valid", "[cast][observer]", owner_types) { 111 | volatile memory_tracker mem_track; 112 | 113 | { 114 | auto run_test = 115 | [&]() { 116 | TestType ptr0 = make_pointer_deleter_1(); 117 | get_object* raw_ptr = ptr0.get(); 118 | observer_ptr optr1 = ptr0; 119 | auto optr2 = oup::dynamic_pointer_cast(optr1); 120 | 121 | using return_type = std::remove_cv_t; 122 | 123 | CHECK(optr1.get() == raw_ptr); 124 | CHECK(optr2.get() == raw_ptr); 125 | CHECK(snitch::type_name == snitch::type_name); 126 | CHECK_INSTANCES(1, 1); 127 | }; 128 | 129 | run_test.template operator(), observer_ptr>(); 130 | run_test.template 131 | operator(), const_observer_ptr>(); 132 | if constexpr (has_base) { 133 | run_test.template 134 | operator(), base_observer_ptr>(); 135 | run_test.template 136 | operator(), get_object, observer_ptr>(); 137 | } 138 | } 139 | 140 | CHECK_NO_LEAKS; 141 | } 142 | 143 | TEMPLATE_LIST_TEST_CASE("observer dynamic_cast copy from empty", "[cast][observer]", owner_types) { 144 | volatile memory_tracker mem_track; 145 | 146 | { 147 | auto run_test = 148 | [&]() { 149 | TestType ptr0 = make_empty_pointer_deleter_1(); 150 | observer_ptr optr1 = ptr0; 151 | auto optr2 = oup::dynamic_pointer_cast(optr1); 152 | 153 | using return_type = std::remove_cv_t; 154 | 155 | CHECK(optr1.get() == nullptr); 156 | CHECK(optr2.get() == nullptr); 157 | CHECK(snitch::type_name == snitch::type_name); 158 | CHECK_INSTANCES(0, 1); 159 | }; 160 | 161 | run_test.template operator(), observer_ptr>(); 162 | run_test.template 163 | operator(), const_observer_ptr>(); 164 | if constexpr (has_base) { 165 | run_test.template 166 | operator(), base_observer_ptr>(); 167 | run_test.template 168 | operator(), get_object, observer_ptr>(); 169 | } 170 | } 171 | 172 | CHECK_NO_LEAKS; 173 | } 174 | 175 | TEMPLATE_LIST_TEST_CASE( 176 | "observer dynamic_cast copy from invalid", "[cast][observer]", owner_types) { 177 | if constexpr (has_base) { 178 | volatile memory_tracker mem_track; 179 | 180 | { 181 | TestType ptr0 = make_pointer_deleter_1(); 182 | get_object* raw_ptr = ptr0.get(); 183 | base_observer_ptr optr1 = ptr0; 184 | auto optr2 = oup::dynamic_pointer_cast(optr1); 185 | 186 | using return_type = std::remove_cv_t; 187 | using expected_return_type = 188 | oup::basic_observer_ptr>; 189 | 190 | CHECK(optr1.get() == raw_ptr); 191 | CHECK(optr2.get() == nullptr); 192 | CHECK(snitch::type_name == snitch::type_name); 193 | CHECK_INSTANCES(1, 1); 194 | } 195 | 196 | CHECK_NO_LEAKS; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_cast_move.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE("observer static_cast move from valid", "[cast][observer]", owner_types) { 5 | volatile memory_tracker mem_track; 6 | 7 | { 8 | auto run_test = [&]() { 9 | TestType ptr1 = make_pointer_deleter_1(); 10 | observer_ptr optr1 = ptr1; 11 | get_object* raw_ptr = ptr1.get(); 12 | auto optr2 = oup::static_pointer_cast(std::move(optr1)); 13 | 14 | using return_type = std::remove_cv_t; 15 | 16 | CHECK(optr1.get() == nullptr); 17 | CHECK(optr2.get() == raw_ptr); 18 | CHECK(snitch::type_name == snitch::type_name); 19 | CHECK_INSTANCES(1, 1); 20 | }; 21 | 22 | run_test.template operator(), observer_ptr>(); 23 | run_test.template operator(), const_observer_ptr>(); 24 | if constexpr (has_base) { 25 | run_test.template operator(), base_observer_ptr>(); 26 | } 27 | } 28 | 29 | CHECK_NO_LEAKS; 30 | } 31 | 32 | TEMPLATE_LIST_TEST_CASE("observer static_cast move from empty", "[cast][observer]", owner_types) { 33 | volatile memory_tracker mem_track; 34 | 35 | { 36 | auto run_test = [&]() { 37 | TestType ptr1 = make_empty_pointer_deleter_1(); 38 | observer_ptr optr1 = ptr1; 39 | auto optr2 = oup::static_pointer_cast(std::move(optr1)); 40 | 41 | using return_type = std::remove_cv_t; 42 | 43 | CHECK(optr1.get() == nullptr); 44 | CHECK(optr2.get() == nullptr); 45 | CHECK(snitch::type_name == snitch::type_name); 46 | CHECK_INSTANCES(0, 1); 47 | }; 48 | 49 | run_test.template operator(), observer_ptr>(); 50 | run_test.template operator(), const_observer_ptr>(); 51 | if constexpr (has_base) { 52 | run_test.template operator(), base_observer_ptr>(); 53 | } 54 | } 55 | 56 | CHECK_NO_LEAKS; 57 | } 58 | 59 | TEMPLATE_LIST_TEST_CASE("observer const_cast move from valid", "[cast][observer]", owner_types) { 60 | volatile memory_tracker mem_track; 61 | 62 | { 63 | auto run_test = [&]() { 64 | TestType ptr1 = make_pointer_deleter_1(); 65 | observer_ptr optr1 = ptr1; 66 | get_object* raw_ptr = ptr1.get(); 67 | auto optr2 = oup::const_pointer_cast(std::move(optr1)); 68 | 69 | using return_type = std::remove_cv_t; 70 | 71 | CHECK(optr1.get() == nullptr); 72 | CHECK(optr2.get() == raw_ptr); 73 | CHECK(snitch::type_name == snitch::type_name); 74 | CHECK_INSTANCES(1, 1); 75 | }; 76 | 77 | run_test.template operator(), const_observer_ptr>(); 78 | run_test.template 79 | operator()>, mutable_observer_ptr>(); 80 | } 81 | 82 | CHECK_NO_LEAKS; 83 | } 84 | 85 | TEMPLATE_LIST_TEST_CASE("observer const_cast move from empty", "[cast][observer]", owner_types) { 86 | volatile memory_tracker mem_track; 87 | 88 | { 89 | auto run_test = [&]() { 90 | TestType ptr1 = make_empty_pointer_deleter_1(); 91 | observer_ptr optr1 = ptr1; 92 | auto optr2 = oup::const_pointer_cast(std::move(optr1)); 93 | 94 | using return_type = std::remove_cv_t; 95 | 96 | CHECK(optr1.get() == nullptr); 97 | CHECK(optr2.get() == nullptr); 98 | CHECK(snitch::type_name == snitch::type_name); 99 | CHECK_INSTANCES(0, 1); 100 | }; 101 | 102 | run_test.template operator(), const_observer_ptr>(); 103 | run_test.template 104 | operator()>, mutable_observer_ptr>(); 105 | } 106 | 107 | CHECK_NO_LEAKS; 108 | } 109 | 110 | TEMPLATE_LIST_TEST_CASE("observer dynamic_cast move from valid", "[cast][observer]", owner_types) { 111 | volatile memory_tracker mem_track; 112 | 113 | { 114 | auto run_test = 115 | [&]() { 116 | TestType ptr0 = make_pointer_deleter_1(); 117 | get_object* raw_ptr = ptr0.get(); 118 | observer_ptr optr1 = ptr0; 119 | auto optr2 = oup::dynamic_pointer_cast(std::move(optr1)); 120 | 121 | using return_type = std::remove_cv_t; 122 | 123 | CHECK(optr1.get() == nullptr); 124 | CHECK(optr2.get() == raw_ptr); 125 | CHECK(snitch::type_name == snitch::type_name); 126 | CHECK_INSTANCES(1, 1); 127 | }; 128 | 129 | run_test.template operator(), observer_ptr>(); 130 | run_test.template 131 | operator(), const_observer_ptr>(); 132 | if constexpr (has_base) { 133 | run_test.template 134 | operator(), base_observer_ptr>(); 135 | run_test.template 136 | operator(), get_object, observer_ptr>(); 137 | } 138 | } 139 | 140 | CHECK_NO_LEAKS; 141 | } 142 | 143 | TEMPLATE_LIST_TEST_CASE("observer dynamic_cast move from empty", "[cast][observer]", owner_types) { 144 | volatile memory_tracker mem_track; 145 | 146 | { 147 | auto run_test = 148 | [&]() { 149 | TestType ptr0 = make_empty_pointer_deleter_1(); 150 | observer_ptr optr1 = ptr0; 151 | auto optr2 = oup::dynamic_pointer_cast(std::move(optr1)); 152 | 153 | using return_type = std::remove_cv_t; 154 | 155 | CHECK(optr1.get() == nullptr); 156 | CHECK(optr2.get() == nullptr); 157 | CHECK(snitch::type_name == snitch::type_name); 158 | CHECK_INSTANCES(0, 1); 159 | }; 160 | 161 | run_test.template operator(), observer_ptr>(); 162 | run_test.template 163 | operator(), const_observer_ptr>(); 164 | if constexpr (has_base) { 165 | run_test.template 166 | operator(), base_observer_ptr>(); 167 | run_test.template 168 | operator(), get_object, observer_ptr>(); 169 | } 170 | } 171 | 172 | CHECK_NO_LEAKS; 173 | } 174 | 175 | TEMPLATE_LIST_TEST_CASE( 176 | "observer dynamic_cast move from invalid", "[cast][observer]", owner_types) { 177 | if constexpr (has_base) { 178 | volatile memory_tracker mem_track; 179 | 180 | { 181 | TestType ptr0 = make_pointer_deleter_1(); 182 | base_observer_ptr optr1 = ptr0; 183 | auto optr2 = oup::dynamic_pointer_cast(std::move(optr1)); 184 | 185 | using return_type = std::remove_cv_t; 186 | using expected_return_type = 187 | oup::basic_observer_ptr>; 188 | 189 | CHECK(optr1.get() == nullptr); 190 | CHECK(optr2.get() == nullptr); 191 | CHECK(snitch::type_name == snitch::type_name); 192 | CHECK_INSTANCES(1, 1); 193 | } 194 | 195 | CHECK_NO_LEAKS; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_comparison.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE( 5 | "observer comparison valid vs nullptr", "[comparison][observer]", owner_types) { 6 | volatile memory_tracker mem_track; 7 | 8 | { 9 | TestType ptr = make_pointer_deleter_1(); 10 | observer_ptr optr{ptr}; 11 | 12 | CHECK(optr != nullptr); 13 | CHECK(!(optr == nullptr)); 14 | CHECK(nullptr != optr); 15 | CHECK(!(nullptr == optr)); 16 | } 17 | 18 | CHECK_NO_LEAKS; 19 | } 20 | 21 | TEMPLATE_LIST_TEST_CASE( 22 | "observer comparison empty vs nullptr", "[comparison][observer]", owner_types) { 23 | volatile memory_tracker mem_track; 24 | 25 | { 26 | observer_ptr optr; 27 | 28 | CHECK(optr == nullptr); 29 | CHECK(!(optr != nullptr)); 30 | CHECK(nullptr == optr); 31 | CHECK(!(nullptr != optr)); 32 | } 33 | 34 | CHECK_NO_LEAKS; 35 | } 36 | 37 | TEMPLATE_LIST_TEST_CASE( 38 | "observer comparison empty vs empty", "[comparison][observer]", owner_types) { 39 | volatile memory_tracker mem_track; 40 | 41 | { 42 | observer_ptr optr1; 43 | observer_ptr optr2; 44 | 45 | CHECK(optr1 == optr2); 46 | CHECK(optr2 == optr1); 47 | CHECK(!(optr1 != optr2)); 48 | CHECK(!(optr2 != optr1)); 49 | } 50 | 51 | CHECK_NO_LEAKS; 52 | } 53 | 54 | TEMPLATE_LIST_TEST_CASE( 55 | "observer comparison empty vs valid", "[comparison][observer]", owner_types) { 56 | volatile memory_tracker mem_track; 57 | 58 | { 59 | observer_ptr optr1; 60 | TestType ptr2 = make_pointer_deleter_2(); 61 | observer_ptr optr2{ptr2}; 62 | 63 | CHECK(optr1 != optr2); 64 | CHECK(optr2 != optr1); 65 | CHECK(!(optr1 == optr2)); 66 | CHECK(!(optr2 == optr1)); 67 | } 68 | 69 | CHECK_NO_LEAKS; 70 | } 71 | 72 | TEMPLATE_LIST_TEST_CASE( 73 | "observer comparison valid vs valid different instance", 74 | "[comparison][observer]", 75 | owner_types) { 76 | volatile memory_tracker mem_track; 77 | 78 | { 79 | TestType ptr1 = make_pointer_deleter_1(); 80 | observer_ptr optr1{ptr1}; 81 | TestType ptr2 = make_pointer_deleter_2(); 82 | observer_ptr optr2{ptr2}; 83 | 84 | CHECK(optr1 != optr2); 85 | CHECK(optr2 != optr1); 86 | CHECK(!(optr1 == optr2)); 87 | CHECK(!(optr2 == optr1)); 88 | } 89 | 90 | CHECK_NO_LEAKS; 91 | } 92 | 93 | TEMPLATE_LIST_TEST_CASE( 94 | "observer comparison valid vs valid same instance", "[comparison][observer]", owner_types) { 95 | volatile memory_tracker mem_track; 96 | 97 | { 98 | TestType ptr = make_pointer_deleter_1(); 99 | observer_ptr optr1{ptr}; 100 | observer_ptr optr2{ptr}; 101 | 102 | CHECK(optr1 == optr2); 103 | CHECK(optr2 == optr1); 104 | CHECK(!(optr1 != optr2)); 105 | CHECK(!(optr2 != optr1)); 106 | } 107 | 108 | CHECK_NO_LEAKS; 109 | } 110 | 111 | TEMPLATE_LIST_TEST_CASE( 112 | "observer comparison valid vs valid different instance derived", 113 | "[comparison][observer]", 114 | owner_types) { 115 | if constexpr (has_base) { 116 | volatile memory_tracker mem_track; 117 | 118 | { 119 | TestType ptr1 = make_pointer_deleter_1(); 120 | observer_ptr optr1{ptr1}; 121 | TestType ptr2 = make_pointer_deleter_2(); 122 | base_observer_ptr optr2{ptr2}; 123 | 124 | CHECK(optr1 != optr2); 125 | CHECK(optr2 != optr1); 126 | CHECK(!(optr1 == optr2)); 127 | CHECK(!(optr2 == optr1)); 128 | } 129 | 130 | CHECK_NO_LEAKS; 131 | } 132 | } 133 | 134 | TEMPLATE_LIST_TEST_CASE( 135 | "observer comparison valid vs valid same instance derived", 136 | "[comparison][observer]", 137 | owner_types) { 138 | if constexpr (has_base) { 139 | volatile memory_tracker mem_track; 140 | 141 | { 142 | TestType ptr = make_pointer_deleter_1(); 143 | observer_ptr optr1{ptr}; 144 | base_observer_ptr optr2{ptr}; 145 | 146 | CHECK(optr1 == optr2); 147 | CHECK(optr2 == optr1); 148 | CHECK(!(optr1 != optr2)); 149 | CHECK(!(optr2 != optr1)); 150 | } 151 | 152 | CHECK_NO_LEAKS; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_construction.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE("observer default constructor", "[construction][observer]", owner_types) { 5 | volatile memory_tracker mem_track; 6 | 7 | { 8 | observer_ptr ptr; 9 | 10 | CHECK(ptr.get() == nullptr); 11 | CHECK(ptr.expired() == true); 12 | CHECK_INSTANCES(0, 0); 13 | } 14 | 15 | CHECK_NO_LEAKS; 16 | } 17 | 18 | TEMPLATE_LIST_TEST_CASE("observer nullptr constructor", "[construction][observer]", owner_types) { 19 | volatile memory_tracker mem_track; 20 | 21 | { 22 | observer_ptr ptr(nullptr); 23 | 24 | CHECK(ptr.get() == nullptr); 25 | CHECK(ptr.expired() == true); 26 | CHECK_INSTANCES(0, 0); 27 | } 28 | 29 | CHECK_NO_LEAKS; 30 | } 31 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_construction_copy.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE( 5 | "observer copy constructor valid", "[construction][observer][from_observer]", owner_types) { 6 | volatile memory_tracker mem_track; 7 | 8 | { 9 | TestType ptr_owner = make_pointer_deleter_1(); 10 | observer_ptr ptr_orig{ptr_owner}; 11 | { 12 | observer_ptr ptr(ptr_orig); 13 | 14 | CHECK(ptr.get() != nullptr); 15 | CHECK(ptr.expired() == false); 16 | CHECK(ptr_orig.get() != nullptr); 17 | CHECK(ptr_orig.expired() == false); 18 | CHECK_INSTANCES(1, 1); 19 | } 20 | 21 | CHECK_INSTANCES(1, 1); 22 | } 23 | 24 | CHECK_NO_LEAKS; 25 | } 26 | 27 | TEMPLATE_LIST_TEST_CASE( 28 | "observer copy constructor empty", "[construction][observer][from_observer]", owner_types) { 29 | volatile memory_tracker mem_track; 30 | 31 | { 32 | observer_ptr ptr_orig; 33 | { 34 | observer_ptr ptr(ptr_orig); 35 | 36 | CHECK(ptr.get() == nullptr); 37 | CHECK(ptr.expired() == true); 38 | CHECK_INSTANCES(0, 0); 39 | } 40 | 41 | CHECK_INSTANCES(0, 0); 42 | } 43 | 44 | CHECK_NO_LEAKS; 45 | } 46 | 47 | TEMPLATE_LIST_TEST_CASE( 48 | "observer copy from valid observer implicit conversion constructor", 49 | "[construction][observer][from_observer]", 50 | owner_types) { 51 | if constexpr (has_base) { 52 | volatile memory_tracker mem_track; 53 | 54 | { 55 | TestType ptr_owner = make_pointer_deleter_1(); 56 | observer_ptr ptr_orig{ptr_owner}; 57 | { 58 | base_observer_ptr ptr{ptr_orig}; 59 | 60 | CHECK(ptr.get() == ptr_owner.get()); 61 | CHECK(ptr.expired() == false); 62 | CHECK(ptr_orig.get() == ptr_owner.get()); 63 | CHECK(ptr_orig.expired() == false); 64 | CHECK_INSTANCES(1, 1); 65 | } 66 | 67 | CHECK_INSTANCES(1, 1); 68 | } 69 | 70 | CHECK_NO_LEAKS; 71 | } 72 | } 73 | 74 | TEMPLATE_LIST_TEST_CASE( 75 | "observer copy from empty observer implicit conversion constructor", 76 | "[construction][observer][from_observer]", 77 | owner_types) { 78 | if constexpr (has_base) { 79 | volatile memory_tracker mem_track; 80 | 81 | { 82 | observer_ptr ptr_orig; 83 | { 84 | base_observer_ptr ptr{ptr_orig}; 85 | 86 | CHECK(ptr.get() == nullptr); 87 | CHECK(ptr.expired() == true); 88 | CHECK(ptr_orig.get() == nullptr); 89 | CHECK(ptr_orig.expired() == true); 90 | CHECK_INSTANCES(0, 0); 91 | } 92 | 93 | CHECK_INSTANCES(0, 0); 94 | } 95 | 96 | CHECK_NO_LEAKS; 97 | } 98 | } 99 | 100 | TEMPLATE_LIST_TEST_CASE( 101 | "observer copy from valid observer explicit conversion constructor", 102 | "[construction][observer][from_observer]", 103 | owner_types) { 104 | if constexpr (has_base) { 105 | volatile memory_tracker mem_track; 106 | 107 | { 108 | base_ptr ptr_owner = make_pointer_deleter_1(); 109 | base_observer_ptr ptr_orig{ptr_owner}; 110 | { 111 | observer_ptr ptr{ 112 | ptr_orig, static_cast*>(ptr_orig.get())}; 113 | 114 | CHECK(ptr.get() == static_cast*>(ptr_owner.get())); 115 | CHECK(ptr.expired() == false); 116 | CHECK(ptr_orig.get() == ptr_owner.get()); 117 | CHECK(ptr_orig.expired() == false); 118 | CHECK_INSTANCES(1, 1); 119 | } 120 | 121 | CHECK_INSTANCES(1, 1); 122 | } 123 | 124 | CHECK_NO_LEAKS; 125 | } 126 | } 127 | 128 | TEMPLATE_LIST_TEST_CASE( 129 | "observer copy from empty observer explicit conversion constructor", 130 | "[construction][observer][from_observer]", 131 | owner_types) { 132 | if constexpr (has_base) { 133 | volatile memory_tracker mem_track; 134 | 135 | { 136 | base_observer_ptr ptr_orig; 137 | { 138 | observer_ptr ptr{ptr_orig, static_cast*>(nullptr)}; 139 | 140 | CHECK(ptr.get() == nullptr); 141 | CHECK(ptr.expired() == true); 142 | CHECK(ptr_orig.get() == nullptr); 143 | CHECK(ptr_orig.expired() == true); 144 | CHECK_INSTANCES(0, 0); 145 | } 146 | 147 | CHECK_INSTANCES(0, 0); 148 | } 149 | 150 | CHECK_NO_LEAKS; 151 | } 152 | } 153 | 154 | TEMPLATE_LIST_TEST_CASE( 155 | "observer copy from valid observer explicit conversion constructor with null", 156 | "[construction][observer][from_observer]", 157 | owner_types) { 158 | if constexpr (has_base) { 159 | volatile memory_tracker mem_track; 160 | 161 | { 162 | base_ptr ptr_owner = make_pointer_deleter_1(); 163 | base_observer_ptr ptr_orig{ptr_owner}; 164 | { 165 | observer_ptr ptr{ptr_orig, static_cast*>(nullptr)}; 166 | 167 | CHECK(ptr.get() == nullptr); 168 | CHECK(ptr.expired() == true); 169 | CHECK(ptr_orig.get() == ptr_owner.get()); 170 | CHECK(ptr_orig.expired() == false); 171 | CHECK_INSTANCES(1, 1); 172 | } 173 | 174 | CHECK_INSTANCES(1, 1); 175 | } 176 | 177 | CHECK_NO_LEAKS; 178 | } 179 | } 180 | 181 | TEMPLATE_LIST_TEST_CASE( 182 | "observer copy from valid observer explicit conversion constructor subobject", 183 | "[construction][observer][from_observer]", 184 | owner_types) { 185 | volatile memory_tracker mem_track; 186 | 187 | { 188 | TestType ptr_owner = make_pointer_deleter_1(); 189 | observer_ptr ptr_orig{ptr_owner}; 190 | { 191 | state_observer_ptr ptr{ptr_orig, &ptr_owner->state_}; 192 | 193 | CHECK(ptr.get() == &ptr_owner->state_); 194 | CHECK(ptr.expired() == false); 195 | CHECK(ptr_orig.get() == ptr_owner.get()); 196 | CHECK(ptr_orig.expired() == false); 197 | CHECK_INSTANCES(1, 1); 198 | } 199 | 200 | CHECK_INSTANCES(1, 1); 201 | } 202 | 203 | CHECK_NO_LEAKS; 204 | } 205 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_construction_from_owner.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE( 5 | "observer from empty owner constructor", "[construction][observer][from_owner]", owner_types) { 6 | volatile memory_tracker mem_track; 7 | 8 | { 9 | TestType ptr_owner; 10 | { 11 | observer_ptr ptr{ptr_owner}; 12 | 13 | CHECK(ptr.get() == ptr_owner.get()); 14 | CHECK(ptr.expired() == true); 15 | CHECK_INSTANCES(0, 1); 16 | } 17 | 18 | CHECK_INSTANCES(0, 1); 19 | } 20 | 21 | CHECK_NO_LEAKS; 22 | } 23 | 24 | TEMPLATE_LIST_TEST_CASE( 25 | "observer from valid owner constructor", "[construction][observer][from_owner]", owner_types) { 26 | volatile memory_tracker mem_track; 27 | 28 | { 29 | TestType ptr_owner = make_pointer_deleter_1(); 30 | { 31 | observer_ptr ptr{ptr_owner}; 32 | 33 | CHECK(ptr.get() == ptr_owner.get()); 34 | CHECK(ptr.expired() == false); 35 | CHECK_INSTANCES(1, 1); 36 | } 37 | 38 | CHECK_INSTANCES(1, 1); 39 | } 40 | 41 | CHECK_NO_LEAKS; 42 | } 43 | 44 | TEMPLATE_LIST_TEST_CASE( 45 | "observer from empty owner conversion constructor", 46 | "[construction][observer][from_owner]", 47 | owner_types) { 48 | if constexpr (has_base) { 49 | volatile memory_tracker mem_track; 50 | 51 | { 52 | TestType ptr_owner; 53 | { 54 | base_observer_ptr ptr{ptr_owner}; 55 | 56 | CHECK(ptr.get() == ptr_owner.get()); 57 | CHECK(ptr.expired() == true); 58 | CHECK_INSTANCES(0, 1); 59 | } 60 | 61 | CHECK_INSTANCES(0, 1); 62 | } 63 | 64 | CHECK_NO_LEAKS; 65 | } 66 | } 67 | 68 | TEMPLATE_LIST_TEST_CASE( 69 | "observer from valid owner conversion constructor", 70 | "[construction][observer][from_owner]", 71 | owner_types) { 72 | if constexpr (has_base) { 73 | volatile memory_tracker mem_track; 74 | 75 | { 76 | TestType ptr_owner = make_pointer_deleter_1(); 77 | { 78 | base_observer_ptr ptr{ptr_owner}; 79 | 80 | CHECK(ptr.get() == ptr_owner.get()); 81 | CHECK(ptr.expired() == false); 82 | CHECK_INSTANCES(1, 1); 83 | } 84 | 85 | CHECK_INSTANCES(1, 1); 86 | } 87 | 88 | CHECK_NO_LEAKS; 89 | } 90 | } 91 | 92 | TEMPLATE_LIST_TEST_CASE( 93 | "observer from empty owner explicit conversion constructor", 94 | "[construction][observer][from_owner]", 95 | owner_types) { 96 | if constexpr (has_base) { 97 | volatile memory_tracker mem_track; 98 | 99 | { 100 | base_ptr ptr_owner; 101 | { 102 | observer_ptr ptr{ptr_owner, static_cast*>(nullptr)}; 103 | 104 | CHECK(ptr.get() == ptr_owner.get()); 105 | CHECK(ptr.expired() == true); 106 | CHECK_INSTANCES(0, 1); 107 | } 108 | 109 | CHECK_INSTANCES(0, 1); 110 | } 111 | 112 | CHECK_NO_LEAKS; 113 | } 114 | } 115 | 116 | TEMPLATE_LIST_TEST_CASE( 117 | "observer from valid owner explicit conversion constructor", 118 | "[construction][observer][from_owner]", 119 | owner_types) { 120 | if constexpr (has_base) { 121 | volatile memory_tracker mem_track; 122 | 123 | { 124 | base_ptr ptr_owner = make_pointer_deleter_1(); 125 | { 126 | observer_ptr ptr{ 127 | ptr_owner, dynamic_cast*>(ptr_owner.get())}; 128 | 129 | CHECK(ptr.get() == ptr_owner.get()); 130 | CHECK(ptr.expired() == false); 131 | CHECK_INSTANCES(1, 1); 132 | } 133 | 134 | CHECK_INSTANCES(1, 1); 135 | } 136 | 137 | CHECK_NO_LEAKS; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_construction_move.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE( 5 | "observer move from valid observer constructor", 6 | "[construction][observer][from_observer]", 7 | owner_types) { 8 | volatile memory_tracker mem_track; 9 | 10 | { 11 | TestType ptr_owner = make_pointer_deleter_1(); 12 | observer_ptr ptr_orig{ptr_owner}; 13 | { 14 | observer_ptr ptr(std::move(ptr_orig)); 15 | CHECK_INSTANCES(1, 1); 16 | CHECK(ptr.get() != nullptr); 17 | CHECK(ptr.expired() == false); 18 | CHECK(ptr_orig.get() == nullptr); 19 | CHECK(ptr_orig.expired() == true); 20 | } 21 | 22 | CHECK_INSTANCES(1, 1); 23 | } 24 | 25 | CHECK_NO_LEAKS; 26 | } 27 | 28 | TEMPLATE_LIST_TEST_CASE( 29 | "observer move from empty observer constructor", 30 | "[construction][observer][from_observer]", 31 | owner_types) { 32 | volatile memory_tracker mem_track; 33 | 34 | { 35 | observer_ptr ptr_orig; 36 | { 37 | observer_ptr ptr(std::move(ptr_orig)); 38 | CHECK_INSTANCES(0, 0); 39 | CHECK(ptr.get() == nullptr); 40 | CHECK(ptr.expired() == true); 41 | CHECK(ptr_orig.get() == nullptr); 42 | CHECK(ptr_orig.expired() == true); 43 | } 44 | 45 | CHECK_INSTANCES(0, 0); 46 | } 47 | 48 | CHECK_NO_LEAKS; 49 | } 50 | 51 | TEMPLATE_LIST_TEST_CASE( 52 | "observer move from valid observer implicit conversion constructor", 53 | "[construction][observer][from_observer]", 54 | owner_types) { 55 | if constexpr (has_base) { 56 | volatile memory_tracker mem_track; 57 | 58 | { 59 | TestType ptr_owner = make_pointer_deleter_1(); 60 | observer_ptr ptr_orig{ptr_owner}; 61 | { 62 | base_observer_ptr ptr{std::move(ptr_orig)}; 63 | CHECK_INSTANCES(1, 1); 64 | CHECK(ptr.get() == ptr_owner.get()); 65 | CHECK(ptr.expired() == false); 66 | CHECK(ptr_orig.get() == nullptr); 67 | CHECK(ptr_orig.expired() == true); 68 | } 69 | 70 | CHECK_INSTANCES(1, 1); 71 | } 72 | 73 | CHECK_NO_LEAKS; 74 | } 75 | } 76 | 77 | TEMPLATE_LIST_TEST_CASE( 78 | "observer move from empty observer implicit conversion constructor", 79 | "[construction][observer][from_observer]", 80 | owner_types) { 81 | volatile memory_tracker mem_track; 82 | 83 | { 84 | observer_ptr ptr_orig; 85 | { 86 | base_observer_ptr ptr{std::move(ptr_orig)}; 87 | 88 | CHECK(ptr.get() == nullptr); 89 | CHECK(ptr.expired() == true); 90 | CHECK(ptr_orig.get() == nullptr); 91 | CHECK(ptr_orig.expired() == true); 92 | CHECK_INSTANCES(0, 0); 93 | } 94 | 95 | CHECK_INSTANCES(0, 0); 96 | } 97 | 98 | CHECK_NO_LEAKS; 99 | } 100 | 101 | TEMPLATE_LIST_TEST_CASE( 102 | "observer move from valid observer explicit conversion constructor", 103 | "[construction][observer][from_observer]", 104 | owner_types) { 105 | 106 | if constexpr (has_base) { 107 | volatile memory_tracker mem_track; 108 | 109 | { 110 | base_ptr ptr_owner = make_pointer_deleter_1(); 111 | base_observer_ptr ptr_orig{ptr_owner}; 112 | { 113 | observer_ptr ptr{ 114 | std::move(ptr_orig), dynamic_cast*>(ptr_orig.get())}; 115 | 116 | CHECK(ptr.get() == dynamic_cast*>(ptr_owner.get())); 117 | CHECK(ptr.expired() == false); 118 | CHECK(ptr_orig.get() == nullptr); 119 | CHECK(ptr_orig.expired() == true); 120 | CHECK_INSTANCES(1, 1); 121 | } 122 | 123 | CHECK_INSTANCES(1, 1); 124 | } 125 | 126 | CHECK_NO_LEAKS; 127 | } 128 | } 129 | 130 | TEMPLATE_LIST_TEST_CASE( 131 | "observer move from empty observer explicit conversion constructor", 132 | "[construction][observer][from_observer]", 133 | owner_types) { 134 | if constexpr (has_base) { 135 | volatile memory_tracker mem_track; 136 | 137 | { 138 | base_observer_ptr ptr_orig; 139 | { 140 | observer_ptr ptr{ 141 | std::move(ptr_orig), static_cast*>(nullptr)}; 142 | 143 | CHECK(ptr.get() == nullptr); 144 | CHECK(ptr.expired() == true); 145 | CHECK(ptr_orig.get() == nullptr); 146 | CHECK(ptr_orig.expired() == true); 147 | CHECK_INSTANCES(0, 0); 148 | } 149 | 150 | CHECK_INSTANCES(0, 0); 151 | } 152 | 153 | CHECK_NO_LEAKS; 154 | } 155 | } 156 | 157 | TEMPLATE_LIST_TEST_CASE( 158 | "observer move from valid observer explicit conversion constructor with null", 159 | "[construction][observer][from_observer]", 160 | owner_types) { 161 | if constexpr (has_base) { 162 | volatile memory_tracker mem_track; 163 | 164 | { 165 | base_ptr ptr_owner = make_pointer_deleter_1(); 166 | base_observer_ptr ptr_orig{ptr_owner}; 167 | { 168 | observer_ptr ptr{ 169 | std::move(ptr_orig), static_cast*>(nullptr)}; 170 | 171 | CHECK(ptr.get() == nullptr); 172 | CHECK(ptr.expired() == true); 173 | CHECK(ptr_orig.get() == nullptr); 174 | CHECK(ptr_orig.expired() == true); 175 | CHECK_INSTANCES(1, 1); 176 | } 177 | 178 | CHECK_INSTANCES(1, 1); 179 | } 180 | 181 | CHECK_NO_LEAKS; 182 | } 183 | } 184 | 185 | TEMPLATE_LIST_TEST_CASE( 186 | "observer move from valid observer explicit conversion constructor subobject", 187 | "[construction][observer][from_observer]", 188 | owner_types) { 189 | volatile memory_tracker mem_track; 190 | 191 | { 192 | TestType ptr_owner = make_pointer_deleter_1(); 193 | observer_ptr ptr_orig{ptr_owner}; 194 | { 195 | state_observer_ptr ptr{std::move(ptr_orig), &ptr_owner->state_}; 196 | 197 | CHECK(ptr.get() == &ptr_owner->state_); 198 | CHECK(ptr.expired() == false); 199 | CHECK(ptr_orig.get() == nullptr); 200 | CHECK(ptr_orig.expired() == true); 201 | CHECK_INSTANCES(1, 1); 202 | } 203 | 204 | CHECK_INSTANCES(1, 1); 205 | } 206 | 207 | CHECK_NO_LEAKS; 208 | } 209 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_from_this.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE("observer from this", "[observer_from_this]", owner_types) { 5 | if constexpr (has_eoft) { 6 | volatile memory_tracker mem_track; 7 | 8 | { 9 | TestType ptr = make_pointer_deleter_1(); 10 | get_object* raw_ptr = ptr.get(); 11 | const get_object* craw_ptr = ptr.get(); 12 | 13 | auto run_checks = [&](auto&& optr, auto&& optr_const) { 14 | using obs_type = std::remove_reference_t; 15 | using const_obs_type = std::remove_reference_t; 16 | CHECK(std::is_const_v == std::is_const_v>); 17 | CHECK(std::is_const_v == true); 18 | 19 | if constexpr (has_eoft_direct_base) { 20 | // For types that inherit directly from eoft. 21 | CHECK((std::is_same_v>) == true); 22 | CHECK((std::is_same_v>) == true); 23 | } else { 24 | // For types that inherit from a base class that inherits from eoft. 25 | CHECK((std::is_base_of_v>) == true); 26 | CHECK( 27 | (std::is_base_of_v< 28 | std::remove_cv_t, get_object>) == true); 29 | } 30 | 31 | CHECK(optr.expired() == false); 32 | CHECK(optr_const.expired() == false); 33 | CHECK(optr.get() == raw_ptr); 34 | CHECK(optr_const.get() == craw_ptr); 35 | CHECK_INSTANCES(1, 1); 36 | }; 37 | 38 | if constexpr (has_eoft_multi_base) { 39 | // Need an explicit choice of which base to call. 40 | auto optr_from_this = raw_ptr->get_eoft::observer_from_this(); 41 | auto optr_from_this_const = craw_ptr->get_eoft::observer_from_this(); 42 | 43 | run_checks(optr_from_this, optr_from_this_const); 44 | } else { 45 | // No ambiguity, just call normally. 46 | auto optr_from_this = raw_ptr->observer_from_this(); 47 | auto optr_from_this_const = craw_ptr->observer_from_this(); 48 | 49 | run_checks(optr_from_this, optr_from_this_const); 50 | } 51 | } 52 | 53 | CHECK_NO_LEAKS; 54 | } 55 | } 56 | 57 | TEMPLATE_LIST_TEST_CASE( 58 | "observer from this with no owner heap", "[observer_from_this]", owner_types) { 59 | if constexpr (has_eoft && !must_use_make_observable) { 60 | volatile memory_tracker mem_track; 61 | 62 | { 63 | get_object* orig_ptr = make_instance(); 64 | 65 | if constexpr (eoft_always_has_block) { 66 | auto optr = make_observer_from_this(orig_ptr); 67 | auto coptr = make_const_observer_from_this(orig_ptr); 68 | 69 | CHECK(optr.expired() == false); 70 | CHECK(optr.get() == orig_ptr); 71 | CHECK(coptr.expired() == false); 72 | CHECK(coptr.get() == orig_ptr); 73 | } else { 74 | REQUIRE_THROWS_MATCHES( 75 | (make_observer_from_this(orig_ptr)), oup::bad_observer_from_this, 76 | snitch::matchers::with_what_contains{ 77 | "observer_from_this() called with uninitialized control block"}); 78 | REQUIRE_THROWS_MATCHES( 79 | (make_const_observer_from_this(orig_ptr)), 80 | oup::bad_observer_from_this, 81 | snitch::matchers::with_what_contains{ 82 | "observer_from_this() called with uninitialized control block"}); 83 | } 84 | 85 | CHECK_INSTANCES(1, 0); 86 | 87 | delete orig_ptr; 88 | } 89 | 90 | CHECK_NO_LEAKS; 91 | } 92 | } 93 | 94 | TEMPLATE_LIST_TEST_CASE("observer from this no owner stack", "[observer_from_this]", owner_types) { 95 | if constexpr (has_eoft && !eoft_constructor_takes_control_block) { 96 | volatile memory_tracker mem_track; 97 | 98 | { 99 | get_object obj; 100 | 101 | if constexpr (eoft_always_has_block) { 102 | auto optr = make_observer_from_this(&obj); 103 | auto coptr = make_const_observer_from_this(&obj); 104 | 105 | CHECK(optr.expired() == false); 106 | CHECK(optr.get() == &obj); 107 | CHECK(coptr.expired() == false); 108 | CHECK(coptr.get() == &obj); 109 | } else { 110 | REQUIRE_THROWS_MATCHES( 111 | (make_observer_from_this(&obj)), oup::bad_observer_from_this, 112 | snitch::matchers::with_what_contains{ 113 | "observer_from_this() called with uninitialized control block"}); 114 | REQUIRE_THROWS_MATCHES( 115 | (make_const_observer_from_this(&obj)), oup::bad_observer_from_this, 116 | snitch::matchers::with_what_contains{ 117 | "observer_from_this() called with uninitialized control block"}); 118 | } 119 | 120 | CHECK_INSTANCES(1, 0); 121 | } 122 | 123 | CHECK_NO_LEAKS; 124 | } 125 | } 126 | 127 | TEMPLATE_LIST_TEST_CASE( 128 | "observer from this acquired into base owner as base", "[observer_from_this]", owner_types) { 129 | if constexpr (has_eoft && !must_use_make_observable) { 130 | volatile memory_tracker mem_track; 131 | 132 | { 133 | get_object* orig_ptr = make_instance(); 134 | get_base_object* orig_base_ptr = orig_ptr; 135 | base_ptr ptr{orig_base_ptr}; 136 | 137 | if constexpr (eoft_always_has_block) { 138 | auto optr = make_observer_from_this(orig_ptr); 139 | auto coptr = make_const_observer_from_this(orig_ptr); 140 | 141 | CHECK(optr.expired() == false); 142 | CHECK(optr.get() == ptr.get()); 143 | CHECK(coptr.expired() == false); 144 | CHECK(coptr.get() == orig_ptr); 145 | CHECK_INSTANCES(1, 1); 146 | } else { 147 | REQUIRE_THROWS_MATCHES( 148 | (make_observer_from_this(orig_ptr)), oup::bad_observer_from_this, 149 | snitch::matchers::with_what_contains{ 150 | "observer_from_this() called with uninitialized control block"}); 151 | REQUIRE_THROWS_MATCHES( 152 | (make_const_observer_from_this(orig_ptr)), 153 | oup::bad_observer_from_this, 154 | snitch::matchers::with_what_contains{ 155 | "observer_from_this() called with uninitialized control block"}); 156 | } 157 | } 158 | 159 | CHECK_NO_LEAKS; 160 | } 161 | } 162 | 163 | TEMPLATE_LIST_TEST_CASE( 164 | "observer from this acquired into base owner as derived", "[observer_from_this]", owner_types) { 165 | if constexpr (has_eoft && has_base && !must_use_make_observable) { 166 | volatile memory_tracker mem_track; 167 | 168 | { 169 | get_object* orig_ptr = make_instance(); 170 | base_ptr ptr{orig_ptr}; 171 | 172 | base_observer_ptr optr = make_observer_from_this(orig_ptr); 173 | 174 | CHECK(optr.expired() == false); 175 | CHECK(optr.get() == ptr.get()); 176 | CHECK_INSTANCES(1, 1); 177 | } 178 | 179 | CHECK_NO_LEAKS; 180 | } 181 | } 182 | 183 | TEMPLATE_LIST_TEST_CASE( 184 | "observer from this after owner reset to empty", "[observer_from_this]", owner_types) { 185 | if constexpr (has_eoft) { 186 | volatile memory_tracker mem_track; 187 | 188 | { 189 | TestType ptr = make_pointer_deleter_1(); 190 | auto optr = make_observer_from_this(ptr.get()); 191 | 192 | CHECK(optr.expired() == false); 193 | CHECK(optr.get() == ptr.get()); 194 | CHECK_INSTANCES(1, 1); 195 | 196 | ptr.reset(); 197 | 198 | CHECK(optr.expired() == true); 199 | CHECK(optr.get() == nullptr); 200 | CHECK_INSTANCES(0, 1); 201 | } 202 | 203 | CHECK_NO_LEAKS; 204 | } 205 | } 206 | 207 | TEMPLATE_LIST_TEST_CASE( 208 | "observer from this after owner reset to valid", "[observer_from_this]", owner_types) { 209 | if constexpr (has_eoft && can_reset_to_new) { 210 | volatile memory_tracker mem_track; 211 | 212 | { 213 | TestType ptr = make_pointer_deleter_1(); 214 | auto optr = make_observer_from_this(ptr.get()); 215 | 216 | CHECK(optr.expired() == false); 217 | CHECK(optr.get() == ptr.get()); 218 | CHECK_INSTANCES(1, 1); 219 | 220 | ptr.reset(make_instance()); 221 | 222 | CHECK(optr.expired() == true); 223 | CHECK(optr.get() == nullptr); 224 | CHECK_INSTANCES(1, 1); 225 | } 226 | 227 | CHECK_NO_LEAKS; 228 | } 229 | } 230 | 231 | TEMPLATE_LIST_TEST_CASE( 232 | "observer from this after owner release", "[observer_from_this]", owner_types) { 233 | if constexpr (has_eoft && can_release) { 234 | volatile memory_tracker mem_track; 235 | 236 | { 237 | TestType ptr = make_pointer_deleter_1(); 238 | auto optr = make_observer_from_this(ptr.get()); 239 | 240 | CHECK(optr.expired() == false); 241 | CHECK(optr.get() == ptr.get()); 242 | CHECK_INSTANCES(1, 1); 243 | 244 | auto* raw_ptr = ptr.release(); 245 | 246 | CHECK(optr.expired() == false); 247 | CHECK(optr.get() == raw_ptr); 248 | CHECK_INSTANCES(1, 1); 249 | 250 | delete raw_ptr; 251 | 252 | CHECK(optr.expired() == true); 253 | CHECK(optr.get() == nullptr); 254 | CHECK_INSTANCES(0, 1); 255 | } 256 | 257 | CHECK_NO_LEAKS; 258 | } 259 | } 260 | 261 | TEMPLATE_LIST_TEST_CASE( 262 | "observer from this after owner release then reset to same", 263 | "[observer_from_this]", 264 | owner_types) { 265 | if constexpr (has_eoft && can_release && can_reset_to_new) { 266 | volatile memory_tracker mem_track; 267 | 268 | { 269 | TestType ptr = make_pointer_deleter_1(); 270 | auto optr = make_observer_from_this(ptr.get()); 271 | 272 | CHECK(optr.expired() == false); 273 | CHECK(optr.get() == ptr.get()); 274 | CHECK_INSTANCES(1, 1); 275 | 276 | auto* raw_ptr = ptr.release(); 277 | 278 | CHECK(optr.expired() == false); 279 | CHECK(optr.get() == raw_ptr); 280 | CHECK_INSTANCES(1, 1); 281 | 282 | ptr.reset(raw_ptr); 283 | 284 | CHECK(optr.expired() == false); 285 | CHECK(optr.get() == raw_ptr); 286 | CHECK_INSTANCES(1, 1); 287 | 288 | ptr.reset(); 289 | 290 | CHECK(optr.expired() == true); 291 | CHECK(optr.get() == nullptr); 292 | CHECK_INSTANCES(0, 1); 293 | } 294 | 295 | CHECK_NO_LEAKS; 296 | } 297 | } 298 | 299 | TEMPLATE_LIST_TEST_CASE( 300 | "observer from this after owner move", "[observer_from_this]", owner_types) { 301 | if constexpr (has_eoft) { 302 | volatile memory_tracker mem_track; 303 | 304 | { 305 | TestType ptr1 = make_pointer_deleter_1(); 306 | TestType ptr2 = std::move(ptr1); 307 | 308 | auto optr = make_observer_from_this(ptr2.get()); 309 | auto coptr = make_const_observer_from_this(ptr2.get()); 310 | 311 | CHECK(optr.expired() == false); 312 | CHECK(optr.get() == ptr2.get()); 313 | CHECK(coptr.expired() == false); 314 | CHECK(coptr.get() == ptr2.get()); 315 | CHECK_INSTANCES(1, 1); 316 | } 317 | 318 | CHECK_NO_LEAKS; 319 | } 320 | } 321 | 322 | TEMPLATE_LIST_TEST_CASE( 323 | "observer from this after owner move assignment", "[observer_from_this]", owner_types) { 324 | if constexpr (has_eoft) { 325 | volatile memory_tracker mem_track; 326 | 327 | { 328 | TestType ptr1 = make_pointer_deleter_1(); 329 | TestType ptr2; 330 | ptr2 = std::move(ptr1); 331 | 332 | auto optr = make_observer_from_this(ptr2.get()); 333 | auto coptr = make_const_observer_from_this(ptr2.get()); 334 | 335 | CHECK(optr.expired() == false); 336 | CHECK(optr.get() == ptr2.get()); 337 | CHECK(coptr.expired() == false); 338 | CHECK(coptr.get() == ptr2.get()); 339 | CHECK_INSTANCES(1, 1); 340 | } 341 | 342 | CHECK_NO_LEAKS; 343 | } 344 | } 345 | 346 | TEST_CASE("observer from this multiple inheritance", "[observer_from_this]") { 347 | using base = test_object_observer_from_this_unique; 348 | using deriv = test_object_observer_from_this_multi_unique; 349 | using ptr_base = oup::observable_unique_ptr; 350 | using ptr_deriv = oup::observable_unique_ptr; 351 | using eoft_base = oup::enable_observer_from_this_unique; 352 | using eoft_deriv = oup::enable_observer_from_this_unique; 353 | using TestType = ptr_deriv; 354 | 355 | volatile memory_tracker mem_track; 356 | 357 | { 358 | deriv* raw_ptr_deriv = new deriv; 359 | base* raw_ptr_base = raw_ptr_deriv; 360 | ptr_deriv ptr(raw_ptr_deriv); 361 | 362 | observer_ptr optr_base = ptr->eoft_base::observer_from_this(); 363 | observer_ptr optr_deriv = ptr->eoft_deriv::observer_from_this(); 364 | 365 | CHECK(optr_base.expired() == false); 366 | CHECK(optr_deriv.expired() == false); 367 | CHECK(optr_base.get() == raw_ptr_base); 368 | CHECK(optr_deriv.get() == raw_ptr_deriv); 369 | CHECK_INSTANCES(1, 1); 370 | } 371 | 372 | CHECK_NO_LEAKS; 373 | } 374 | 375 | TEMPLATE_LIST_TEST_CASE("observer from this in constructor", "[observer_from_this]", owner_types) { 376 | if constexpr (has_eoft && has_eoft_self_member) { 377 | volatile memory_tracker mem_track; 378 | 379 | if constexpr (eoft_always_has_block) { 380 | next_test_object_constructor_calls_observer_from_this = true; 381 | TestType ptr = make_pointer_deleter_1(); 382 | next_test_object_constructor_calls_observer_from_this = false; 383 | CHECK(ptr->self == ptr.get()); 384 | 385 | CHECK_INSTANCES(1, 1); 386 | } else { 387 | next_test_object_constructor_calls_observer_from_this = true; 388 | REQUIRE_THROWS_MATCHES( 389 | (make_pointer_deleter_1()), oup::bad_observer_from_this, 390 | snitch::matchers::with_what_contains{ 391 | "observer_from_this() called with uninitialized control block"}); 392 | next_test_object_constructor_calls_observer_from_this = false; 393 | } 394 | 395 | CHECK_NO_LEAKS; 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /tests/runtime_tests_observer_misc.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE("observer size", "[size][observer]", owner_types) { 5 | CHECK(sizeof(observer_ptr) == 2 * sizeof(void*)); 6 | } 7 | 8 | TEMPLATE_LIST_TEST_CASE("observer reset to null", "[reset][observer]", owner_types) { 9 | volatile memory_tracker mem_track; 10 | 11 | { 12 | TestType ptr = make_pointer_deleter_1(); 13 | observer_ptr optr{ptr}; 14 | optr.reset(); 15 | CHECK(optr.get() == nullptr); 16 | CHECK(optr.expired() == true); 17 | CHECK_INSTANCES(1, 1); 18 | } 19 | 20 | CHECK_NO_LEAKS; 21 | } 22 | 23 | TEMPLATE_LIST_TEST_CASE("observer swap empty vs empty", "[swap][observer]", owner_types) { 24 | volatile memory_tracker mem_track; 25 | 26 | { 27 | observer_ptr optr1; 28 | observer_ptr optr2; 29 | optr2.swap(optr1); 30 | CHECK_INSTANCES(0, 0); 31 | CHECK(optr1.get() == nullptr); 32 | CHECK(optr2.get() == nullptr); 33 | CHECK(optr1.expired() == true); 34 | CHECK(optr2.expired() == true); 35 | } 36 | 37 | CHECK_NO_LEAKS; 38 | } 39 | 40 | TEMPLATE_LIST_TEST_CASE("observer swap valid vs empty", "[swap][observer]", owner_types) { 41 | volatile memory_tracker mem_track; 42 | 43 | { 44 | TestType ptr1 = make_pointer_deleter_1(); 45 | observer_ptr optr1{ptr1}; 46 | observer_ptr optr2; 47 | optr2.swap(optr1); 48 | CHECK_INSTANCES(1, 1); 49 | CHECK(optr1.get() == nullptr); 50 | CHECK(optr2.get() != nullptr); 51 | CHECK(optr2.get() == ptr1.get()); 52 | CHECK(optr1.expired() == true); 53 | CHECK(optr2.expired() == false); 54 | } 55 | 56 | CHECK_NO_LEAKS; 57 | } 58 | 59 | TEMPLATE_LIST_TEST_CASE("observer swap empty vs valid", "[swap][observer]", owner_types) { 60 | volatile memory_tracker mem_track; 61 | 62 | { 63 | TestType ptr2 = make_pointer_deleter_2(); 64 | observer_ptr optr1; 65 | observer_ptr optr2{ptr2}; 66 | optr2.swap(optr1); 67 | CHECK_INSTANCES(1, 1); 68 | CHECK(optr1.get() != nullptr); 69 | CHECK(optr1.get() == ptr2.get()); 70 | CHECK(optr2.get() == nullptr); 71 | CHECK(optr1.expired() == false); 72 | CHECK(optr2.expired() == true); 73 | } 74 | 75 | CHECK_NO_LEAKS; 76 | } 77 | 78 | TEMPLATE_LIST_TEST_CASE("observer swap valid vs valid", "[swap][observer]", owner_types) { 79 | volatile memory_tracker mem_track; 80 | 81 | { 82 | TestType ptr1 = make_pointer_deleter_1(); 83 | TestType ptr2 = make_pointer_deleter_2(); 84 | observer_ptr optr1{ptr1}; 85 | observer_ptr optr2{ptr2}; 86 | optr2.swap(optr1); 87 | CHECK_INSTANCES(2, 2); 88 | CHECK(optr1.get() != ptr1.get()); 89 | CHECK(optr1.get() == ptr2.get()); 90 | CHECK(optr2.get() != ptr2.get()); 91 | CHECK(optr2.get() == ptr1.get()); 92 | CHECK(optr1.expired() == false); 93 | CHECK(optr2.expired() == false); 94 | } 95 | 96 | CHECK_NO_LEAKS; 97 | } 98 | 99 | TEMPLATE_LIST_TEST_CASE( 100 | "observer swap valid vs valid same instance", "[swap][observer]", owner_types) { 101 | volatile memory_tracker mem_track; 102 | 103 | { 104 | TestType ptr = make_pointer_deleter_1(); 105 | observer_ptr optr1{ptr}; 106 | observer_ptr optr2{ptr}; 107 | optr2.swap(optr1); 108 | CHECK_INSTANCES(1, 1); 109 | CHECK(optr1.get() == ptr.get()); 110 | CHECK(optr2.get() == ptr.get()); 111 | CHECK(optr1.expired() == false); 112 | CHECK(optr2.expired() == false); 113 | } 114 | 115 | CHECK_NO_LEAKS; 116 | } 117 | 118 | TEMPLATE_LIST_TEST_CASE("observer swap self vs self empty", "[swap][observer]", owner_types) { 119 | volatile memory_tracker mem_track; 120 | 121 | { 122 | observer_ptr optr; 123 | optr.swap(optr); 124 | CHECK_INSTANCES(0, 0); 125 | CHECK(optr.get() == nullptr); 126 | CHECK(optr.expired() == true); 127 | } 128 | 129 | CHECK_NO_LEAKS; 130 | } 131 | 132 | TEMPLATE_LIST_TEST_CASE("observer swap self vs self valid", "[swap][observer]", owner_types) { 133 | volatile memory_tracker mem_track; 134 | 135 | { 136 | TestType ptr = make_pointer_deleter_1(); 137 | observer_ptr optr{ptr}; 138 | optr.swap(optr); 139 | CHECK_INSTANCES(1, 1); 140 | CHECK(optr.get() == ptr.get()); 141 | CHECK(optr.expired() == false); 142 | } 143 | 144 | CHECK_NO_LEAKS; 145 | } 146 | 147 | TEMPLATE_LIST_TEST_CASE("observer dereference valid", "[dereference][observer]", owner_types) { 148 | volatile memory_tracker mem_track; 149 | 150 | { 151 | TestType ptr = make_pointer_deleter_1(); 152 | observer_ptr optr{ptr}; 153 | CHECK(optr->state_ == test_object::state::default_init); 154 | CHECK((*optr).state_ == test_object::state::default_init); 155 | } 156 | 157 | CHECK_NO_LEAKS; 158 | } 159 | 160 | TEMPLATE_LIST_TEST_CASE("observer get valid", "[get][observer]", owner_types) { 161 | volatile memory_tracker mem_track; 162 | 163 | { 164 | TestType ptr = make_pointer_deleter_1(); 165 | observer_ptr optr{ptr}; 166 | CHECK(optr.get() != nullptr); 167 | CHECK(optr.get()->state_ == test_object::state::default_init); 168 | } 169 | 170 | CHECK_NO_LEAKS; 171 | } 172 | 173 | TEMPLATE_LIST_TEST_CASE("observer get empty", "[get][observer]", owner_types) { 174 | volatile memory_tracker mem_track; 175 | 176 | { 177 | observer_ptr optr; 178 | CHECK(optr.get() == nullptr); 179 | } 180 | 181 | CHECK_NO_LEAKS; 182 | } 183 | 184 | TEMPLATE_LIST_TEST_CASE("observer raw_get valid", "[raw_get][observer]", owner_types) { 185 | volatile memory_tracker mem_track; 186 | 187 | { 188 | TestType ptr = make_pointer_deleter_1(); 189 | observer_ptr optr{ptr}; 190 | CHECK(optr.raw_get() != nullptr); 191 | CHECK(optr.raw_get()->state_ == test_object::state::default_init); 192 | } 193 | 194 | CHECK_NO_LEAKS; 195 | } 196 | 197 | TEMPLATE_LIST_TEST_CASE("observer raw_get empty", "[raw_get][observer]", owner_types) { 198 | volatile memory_tracker mem_track; 199 | 200 | { 201 | observer_ptr optr; 202 | CHECK(optr.raw_get() == nullptr); 203 | } 204 | 205 | CHECK_NO_LEAKS; 206 | } 207 | 208 | TEMPLATE_LIST_TEST_CASE("observer operator bool valid", "[bool][observer]", owner_types) { 209 | volatile memory_tracker mem_track; 210 | 211 | { 212 | TestType ptr = make_pointer_deleter_1(); 213 | observer_ptr optr{ptr}; 214 | if (optr) { 215 | } else { 216 | FAIL("if (optr) should have been true"); 217 | } 218 | } 219 | 220 | CHECK_NO_LEAKS; 221 | } 222 | 223 | TEMPLATE_LIST_TEST_CASE("observer operator bool empty", "[bool][observer]", owner_types) { 224 | volatile memory_tracker mem_track; 225 | 226 | { 227 | observer_ptr optr; 228 | if (optr) { 229 | FAIL("if (optr) should have been false"); 230 | } 231 | } 232 | 233 | CHECK_NO_LEAKS; 234 | } 235 | -------------------------------------------------------------------------------- /tests/runtime_tests_owner_assignment_move.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE( 5 | "owner move assignment operator valid to empty", "[assignment][owner]", owner_types) { 6 | volatile memory_tracker mem_track; 7 | 8 | { 9 | TestType ptr_orig = make_pointer_deleter_1(); 10 | { 11 | TestType ptr = make_empty_pointer_deleter_2(); 12 | ptr = std::move(ptr_orig); 13 | 14 | CHECK(ptr.get() != nullptr); 15 | if constexpr (has_stateful_deleter) { 16 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 17 | } 18 | CHECK_INSTANCES(1, 2); 19 | } 20 | 21 | CHECK_INSTANCES(0, 1); 22 | } 23 | 24 | CHECK_NO_LEAKS; 25 | } 26 | 27 | TEMPLATE_LIST_TEST_CASE( 28 | "owner move assignment operator empty to valid", "[assignment][owner]", owner_types) { 29 | volatile memory_tracker mem_track; 30 | 31 | { 32 | TestType ptr_orig = make_empty_pointer_deleter_1(); 33 | { 34 | TestType ptr = make_pointer_deleter_2(); 35 | ptr = std::move(ptr_orig); 36 | 37 | CHECK(ptr.get() == nullptr); 38 | if constexpr (has_stateful_deleter) { 39 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 40 | } 41 | CHECK_INSTANCES(0, 2); 42 | } 43 | 44 | CHECK_INSTANCES(0, 1); 45 | } 46 | 47 | CHECK_NO_LEAKS; 48 | } 49 | 50 | TEMPLATE_LIST_TEST_CASE( 51 | "owner move assignment operator empty to empty", "[assignment][owner]", owner_types) { 52 | volatile memory_tracker mem_track; 53 | 54 | { 55 | TestType ptr_orig = make_empty_pointer_deleter_1(); 56 | { 57 | TestType ptr = make_empty_pointer_deleter_2(); 58 | ptr = std::move(ptr_orig); 59 | 60 | CHECK(ptr.get() == nullptr); 61 | if constexpr (has_stateful_deleter) { 62 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 63 | } 64 | CHECK_INSTANCES(0, 2); 65 | } 66 | 67 | CHECK_INSTANCES(0, 1); 68 | } 69 | 70 | CHECK_NO_LEAKS; 71 | } 72 | 73 | TEMPLATE_LIST_TEST_CASE( 74 | "owner move assignment operator valid to valid", "[assignment][owner]", owner_types) { 75 | volatile memory_tracker mem_track; 76 | 77 | { 78 | TestType ptr_orig = make_pointer_deleter_1(); 79 | auto* raw_ptr_orig = ptr_orig.get(); 80 | { 81 | TestType ptr = make_pointer_deleter_1(); 82 | ptr = std::move(ptr_orig); 83 | 84 | CHECK(ptr.get() == raw_ptr_orig); 85 | if constexpr (has_stateful_deleter) { 86 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 87 | } 88 | CHECK_INSTANCES(1, 2); 89 | } 90 | 91 | CHECK_INSTANCES(0, 1); 92 | } 93 | 94 | CHECK_NO_LEAKS; 95 | } 96 | 97 | TEMPLATE_LIST_TEST_CASE( 98 | "owner move assignment converting operator valid to empty", 99 | "[assignment][owner]", 100 | owner_types) { 101 | volatile memory_tracker mem_track; 102 | 103 | { 104 | TestType ptr_orig = make_pointer_deleter_1(); 105 | { 106 | base_ptr ptr = make_empty_pointer_deleter_2>(); 107 | ptr = std::move(ptr_orig); 108 | 109 | CHECK(ptr.get() != nullptr); 110 | if constexpr (has_stateful_deleter) { 111 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 112 | } 113 | CHECK_INSTANCES(1, 2); 114 | } 115 | 116 | CHECK_INSTANCES(0, 1); 117 | } 118 | 119 | CHECK_NO_LEAKS; 120 | } 121 | 122 | TEMPLATE_LIST_TEST_CASE( 123 | "owner move assignment converting operator empty to valid", 124 | "[assignment][owner]", 125 | owner_types) { 126 | volatile memory_tracker mem_track; 127 | 128 | { 129 | TestType ptr_orig = make_empty_pointer_deleter_1(); 130 | { 131 | base_ptr ptr = make_pointer_deleter_2>(); 132 | ptr = std::move(ptr_orig); 133 | 134 | CHECK(ptr.get() == nullptr); 135 | if constexpr (has_stateful_deleter) { 136 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 137 | } 138 | CHECK_INSTANCES(0, 2); 139 | } 140 | 141 | CHECK_INSTANCES(0, 1); 142 | } 143 | 144 | CHECK_NO_LEAKS; 145 | } 146 | 147 | TEMPLATE_LIST_TEST_CASE( 148 | "owner move assignment converting operator empty to empty", 149 | "[assignment][owner]", 150 | owner_types) { 151 | volatile memory_tracker mem_track; 152 | 153 | { 154 | TestType ptr_orig = make_empty_pointer_deleter_1(); 155 | { 156 | base_ptr ptr = make_empty_pointer_deleter_2>(); 157 | ptr = std::move(ptr_orig); 158 | 159 | CHECK(ptr.get() == nullptr); 160 | if constexpr (has_stateful_deleter) { 161 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 162 | } 163 | CHECK_INSTANCES(0, 2); 164 | } 165 | 166 | CHECK_INSTANCES(0, 1); 167 | } 168 | 169 | CHECK_NO_LEAKS; 170 | } 171 | 172 | TEMPLATE_LIST_TEST_CASE( 173 | "owner move assignment converting operator valid to valid", 174 | "[assignment][owner]", 175 | owner_types) { 176 | volatile memory_tracker mem_track; 177 | 178 | { 179 | TestType ptr_orig = make_pointer_deleter_1(); 180 | auto* raw_ptr_orig = ptr_orig.get(); 181 | { 182 | base_ptr ptr = make_pointer_deleter_1>(); 183 | ptr = std::move(ptr_orig); 184 | 185 | CHECK(ptr.get() == raw_ptr_orig); 186 | if constexpr (has_stateful_deleter) { 187 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 188 | } 189 | CHECK_INSTANCES(1, 2); 190 | } 191 | 192 | CHECK_INSTANCES(0, 1); 193 | } 194 | 195 | CHECK_NO_LEAKS; 196 | } 197 | 198 | TEMPLATE_LIST_TEST_CASE( 199 | "owner move assignment operator self to self valid", "[assignment][owner]", owner_types) { 200 | volatile memory_tracker mem_track; 201 | 202 | { 203 | TestType ptr = make_pointer_deleter_1(); 204 | ptr = std::move(ptr); 205 | 206 | CHECK(ptr.get() == nullptr); 207 | if constexpr (has_stateful_deleter) { 208 | CHECK(ptr.get_deleter().state_ == test_deleter::state::empty); 209 | } 210 | CHECK_INSTANCES(0, 1); 211 | } 212 | 213 | CHECK_NO_LEAKS; 214 | } 215 | 216 | TEMPLATE_LIST_TEST_CASE( 217 | "owner move assignment operator self to self empty", "[assignment][owner]", owner_types) { 218 | volatile memory_tracker mem_track; 219 | 220 | { 221 | TestType ptr = make_empty_pointer_deleter_1(); 222 | ptr = std::move(ptr); 223 | 224 | CHECK(ptr.get() == nullptr); 225 | if constexpr (has_stateful_deleter) { 226 | CHECK(ptr.get_deleter().state_ == test_deleter::state::empty); 227 | } 228 | CHECK_INSTANCES(0, 1); 229 | } 230 | 231 | CHECK_NO_LEAKS; 232 | } 233 | -------------------------------------------------------------------------------- /tests/runtime_tests_owner_cast_move.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | // For std::bad_cast 5 | #include 6 | 7 | TEMPLATE_LIST_TEST_CASE("owner static_cast move from valid", "[cast][owner]", owner_types) { 8 | volatile memory_tracker mem_track; 9 | 10 | { 11 | auto run_test = [&]() { 12 | TestType ptr1 = make_pointer_deleter_1(); 13 | get_object* raw_ptr = ptr1.get(); 14 | auto ptr2 = oup::static_pointer_cast(std::move(ptr1)); 15 | 16 | using return_type = std::remove_cv_t; 17 | 18 | CHECK(ptr1.get() == nullptr); 19 | CHECK(ptr2.get() == raw_ptr); 20 | CHECK(snitch::type_name == snitch::type_name); 21 | if constexpr (has_stateful_deleter) { 22 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 23 | } 24 | CHECK_INSTANCES(1, 2); 25 | }; 26 | 27 | run_test.template operator(), TestType>(); 28 | run_test.template operator(), const_ptr>(); 29 | if constexpr (has_base) { 30 | run_test.template operator(), base_ptr>(); 31 | } 32 | } 33 | 34 | CHECK_NO_LEAKS; 35 | } 36 | 37 | TEMPLATE_LIST_TEST_CASE("owner static_cast move from empty", "[cast][owner]", owner_types) { 38 | volatile memory_tracker mem_track; 39 | 40 | { 41 | auto run_test = [&]() { 42 | TestType ptr1 = make_empty_pointer_deleter_1(); 43 | auto ptr2 = oup::static_pointer_cast(std::move(ptr1)); 44 | 45 | using return_type = std::remove_cv_t; 46 | 47 | CHECK(ptr1.get() == nullptr); 48 | CHECK(ptr2.get() == nullptr); 49 | CHECK(snitch::type_name == snitch::type_name); 50 | if constexpr (has_stateful_deleter) { 51 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 52 | } 53 | CHECK_INSTANCES(0, 2); 54 | }; 55 | 56 | run_test.template operator(), TestType>(); 57 | run_test.template operator(), const_ptr>(); 58 | if constexpr (has_base) { 59 | run_test.template operator(), base_ptr>(); 60 | } 61 | } 62 | 63 | CHECK_NO_LEAKS; 64 | } 65 | 66 | TEMPLATE_LIST_TEST_CASE("owner const_cast move from valid", "[cast][owner]", owner_types) { 67 | volatile memory_tracker mem_track; 68 | 69 | { 70 | auto run_test = [&]() { 71 | TestType ptr1 = make_pointer_deleter_1(); 72 | get_object* raw_ptr = ptr1.get(); 73 | auto ptr2 = oup::const_pointer_cast(std::move(ptr1)); 74 | 75 | using return_type = std::remove_cv_t; 76 | 77 | CHECK(ptr1.get() == nullptr); 78 | CHECK(ptr2.get() == raw_ptr); 79 | CHECK(snitch::type_name == snitch::type_name); 80 | if constexpr (has_stateful_deleter) { 81 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 82 | } 83 | CHECK_INSTANCES(1, 2); 84 | }; 85 | 86 | run_test.template operator(), const_ptr>(); 87 | run_test 88 | .template operator()>, mutable_ptr>(); 89 | } 90 | 91 | CHECK_NO_LEAKS; 92 | } 93 | 94 | TEMPLATE_LIST_TEST_CASE("owner const_cast move from empty", "[cast][owner]", owner_types) { 95 | volatile memory_tracker mem_track; 96 | 97 | { 98 | auto run_test = [&]() { 99 | TestType ptr1 = make_empty_pointer_deleter_1(); 100 | auto ptr2 = oup::const_pointer_cast(std::move(ptr1)); 101 | 102 | using return_type = std::remove_cv_t; 103 | 104 | CHECK(ptr1.get() == nullptr); 105 | CHECK(ptr2.get() == nullptr); 106 | CHECK(snitch::type_name == snitch::type_name); 107 | if constexpr (has_stateful_deleter) { 108 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 109 | } 110 | CHECK_INSTANCES(0, 2); 111 | }; 112 | 113 | run_test.template operator(), const_ptr>(); 114 | run_test 115 | .template operator()>, mutable_ptr>(); 116 | } 117 | 118 | CHECK_NO_LEAKS; 119 | } 120 | 121 | TEMPLATE_LIST_TEST_CASE("owner dynamic_cast move from valid", "[cast][owner]", owner_types) { 122 | volatile memory_tracker mem_track; 123 | 124 | { 125 | auto run_test = 126 | [&]() { 127 | TestType ptr0 = make_pointer_deleter_1(); 128 | get_object* raw_ptr = ptr0.get(); 129 | start_type ptr1 = std::move(ptr0); 130 | auto ptr2 = oup::dynamic_pointer_cast(std::move(ptr1)); 131 | 132 | using return_type = std::remove_cv_t; 133 | 134 | CHECK(ptr1.get() == nullptr); 135 | CHECK(ptr2.get() == raw_ptr); 136 | CHECK(snitch::type_name == snitch::type_name); 137 | if constexpr (has_stateful_deleter) { 138 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 139 | } 140 | CHECK_INSTANCES(1, 3); 141 | }; 142 | 143 | run_test.template operator(), TestType>(); 144 | run_test.template operator(), const_ptr>(); 145 | if constexpr (has_base) { 146 | run_test.template operator(), base_ptr>(); 147 | run_test.template operator(), get_object, TestType>(); 148 | } 149 | } 150 | 151 | CHECK_NO_LEAKS; 152 | } 153 | 154 | TEMPLATE_LIST_TEST_CASE("owner dynamic_cast move from empty", "[cast][owner]", owner_types) { 155 | volatile memory_tracker mem_track; 156 | 157 | { 158 | auto run_test = 159 | [&]() { 160 | TestType ptr0 = make_empty_pointer_deleter_1(); 161 | start_type ptr1 = std::move(ptr0); 162 | auto ptr2 = oup::dynamic_pointer_cast(std::move(ptr1)); 163 | 164 | using return_type = std::remove_cv_t; 165 | 166 | CHECK(ptr1.get() == nullptr); 167 | CHECK(ptr2.get() == nullptr); 168 | CHECK(snitch::type_name == snitch::type_name); 169 | if constexpr (has_stateful_deleter) { 170 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 171 | } 172 | CHECK_INSTANCES(0, 3); 173 | }; 174 | 175 | run_test.template operator(), TestType>(); 176 | run_test.template operator(), const_ptr>(); 177 | if constexpr (has_base) { 178 | run_test.template operator(), base_ptr>(); 179 | run_test.template operator(), get_object, TestType>(); 180 | } 181 | } 182 | 183 | CHECK_NO_LEAKS; 184 | } 185 | 186 | TEMPLATE_LIST_TEST_CASE("owner dynamic_cast move from invalid", "[cast][owner]", owner_types) { 187 | if constexpr (has_base) { 188 | volatile memory_tracker mem_track; 189 | 190 | { 191 | TestType ptr0 = make_pointer_deleter_1(); 192 | get_object* raw_ptr = ptr0.get(); 193 | base_ptr ptr1 = std::move(ptr0); 194 | 195 | CHECK_THROWS_AS( 196 | (oup::dynamic_pointer_cast(std::move(ptr1))), std::bad_cast); 197 | 198 | CHECK(ptr1.get() == raw_ptr); 199 | if constexpr (has_stateful_deleter) { 200 | CHECK(ptr1.get_deleter().state_ == test_deleter::state::special_init_1); 201 | } 202 | CHECK_INSTANCES(1, 2); 203 | } 204 | 205 | CHECK_NO_LEAKS; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /tests/runtime_tests_owner_comparison.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE("owner comparison valid vs nullptr", "[comparison][owner]", owner_types) { 5 | volatile memory_tracker mem_track; 6 | 7 | { 8 | TestType ptr = make_pointer_deleter_1(); 9 | 10 | CHECK(ptr != nullptr); 11 | CHECK(!(ptr == nullptr)); 12 | CHECK(nullptr != ptr); 13 | CHECK(!(nullptr == ptr)); 14 | } 15 | 16 | CHECK_NO_LEAKS; 17 | } 18 | 19 | TEMPLATE_LIST_TEST_CASE("owner comparison empty vs nullptr", "[comparison][owner]", owner_types) { 20 | volatile memory_tracker mem_track; 21 | 22 | { 23 | TestType ptr = make_empty_pointer_deleter_1(); 24 | 25 | CHECK(ptr == nullptr); 26 | CHECK(!(ptr != nullptr)); 27 | CHECK(nullptr == ptr); 28 | CHECK(!(nullptr != ptr)); 29 | } 30 | 31 | CHECK_NO_LEAKS; 32 | } 33 | 34 | TEMPLATE_LIST_TEST_CASE("owner comparison empty vs empty", "[comparison][owner]", owner_types) { 35 | volatile memory_tracker mem_track; 36 | 37 | { 38 | TestType ptr1 = make_empty_pointer_deleter_1(); 39 | TestType ptr2 = make_empty_pointer_deleter_2(); 40 | 41 | CHECK(ptr1 == ptr2); 42 | CHECK(ptr2 == ptr1); 43 | CHECK(!(ptr1 != ptr2)); 44 | CHECK(!(ptr2 != ptr1)); 45 | } 46 | 47 | CHECK_NO_LEAKS; 48 | } 49 | 50 | TEMPLATE_LIST_TEST_CASE("owner comparison empty vs valid", "[comparison][owner]", owner_types) { 51 | volatile memory_tracker mem_track; 52 | 53 | { 54 | TestType ptr1 = make_empty_pointer_deleter_1(); 55 | TestType ptr2 = make_pointer_deleter_2(); 56 | 57 | CHECK(ptr1 != ptr2); 58 | CHECK(ptr2 != ptr1); 59 | CHECK(!(ptr1 == ptr2)); 60 | CHECK(!(ptr2 == ptr1)); 61 | } 62 | 63 | CHECK_NO_LEAKS; 64 | } 65 | 66 | TEMPLATE_LIST_TEST_CASE("owner comparison valid vs valid", "[comparison][owner]", owner_types) { 67 | volatile memory_tracker mem_track; 68 | 69 | { 70 | TestType ptr1 = make_pointer_deleter_1(); 71 | TestType ptr2 = make_pointer_deleter_2(); 72 | 73 | CHECK(ptr1 != ptr2); 74 | CHECK(ptr2 != ptr1); 75 | CHECK(!(ptr1 == ptr2)); 76 | CHECK(!(ptr2 == ptr1)); 77 | } 78 | 79 | CHECK_NO_LEAKS; 80 | } 81 | -------------------------------------------------------------------------------- /tests/runtime_tests_owner_construction.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE("owner default constructor", "[construction][owner]", owner_types) { 5 | volatile memory_tracker mem_track; 6 | 7 | { 8 | TestType ptr; 9 | 10 | CHECK(ptr.get() == nullptr); 11 | if constexpr (has_stateful_deleter) { 12 | CHECK(ptr.get_deleter().state_ == test_deleter::state::default_init); 13 | } 14 | CHECK_INSTANCES(0, 1); 15 | } 16 | 17 | CHECK_NO_LEAKS; 18 | } 19 | 20 | TEMPLATE_LIST_TEST_CASE("owner nullptr constructor", "[construction][owner]", owner_types) { 21 | volatile memory_tracker mem_track; 22 | 23 | { 24 | TestType ptr(nullptr); 25 | 26 | CHECK(ptr.get() == nullptr); 27 | if constexpr (has_stateful_deleter) { 28 | CHECK(ptr.get_deleter().state_ == test_deleter::state::default_init); 29 | } 30 | CHECK_INSTANCES(0, 1); 31 | } 32 | 33 | CHECK_NO_LEAKS; 34 | } 35 | 36 | TEMPLATE_LIST_TEST_CASE("owner move constructor", "[construction][owner]", owner_types) { 37 | volatile memory_tracker mem_track; 38 | 39 | { 40 | TestType ptr_orig = make_pointer_deleter_1(); 41 | { 42 | TestType ptr(std::move(ptr_orig)); 43 | 44 | CHECK(ptr.get() != nullptr); 45 | if constexpr (has_stateful_deleter) { 46 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 47 | } 48 | CHECK_INSTANCES(1, 2); 49 | } 50 | 51 | CHECK_INSTANCES(0, 1); 52 | } 53 | 54 | CHECK_NO_LEAKS; 55 | } 56 | 57 | TEMPLATE_LIST_TEST_CASE("owner acquiring constructor", "[construction][owner]", owner_types) { 58 | if constexpr (!must_use_make_observable) { 59 | volatile memory_tracker mem_track; 60 | 61 | { 62 | TestType ptr(make_instance()); 63 | 64 | CHECK(ptr.get() != nullptr); 65 | if constexpr (has_stateful_deleter) { 66 | CHECK(ptr.get_deleter().state_ == test_deleter::state::default_init); 67 | } 68 | CHECK_INSTANCES(1, 1); 69 | } 70 | 71 | CHECK_NO_LEAKS; 72 | } 73 | } 74 | 75 | TEMPLATE_LIST_TEST_CASE( 76 | "owner acquiring constructor with deleter", "[construction][owner]", owner_types) { 77 | if constexpr (!must_use_make_observable && has_stateful_deleter) { 78 | volatile memory_tracker mem_track; 79 | 80 | { 81 | TestType ptr(make_instance(), make_deleter_instance_1()); 82 | 83 | CHECK(ptr.get() != nullptr); 84 | if constexpr (has_stateful_deleter) { 85 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 86 | } 87 | CHECK_INSTANCES(1, 1); 88 | } 89 | 90 | CHECK_NO_LEAKS; 91 | } 92 | } 93 | 94 | TEMPLATE_LIST_TEST_CASE( 95 | "owner acquiring constructor bad alloc", "[construction][owner]", owner_types) { 96 | if constexpr (!must_use_make_observable) { 97 | volatile memory_tracker mem_track; 98 | 99 | { 100 | auto* raw_ptr = make_instance(); 101 | if constexpr (eoft_allocates) { 102 | fail_next_allocation{}, TestType{raw_ptr}; 103 | } else { 104 | #if !defined(OUP_COMPILER_LLVM) || !defined(NDEBUG) 105 | REQUIRE_THROWS_AS((fail_next_allocation{}, TestType{raw_ptr}), std::bad_alloc); 106 | #else 107 | // LLVM in Release mode is able to inline the allocation, bypassing our 108 | // custom allocator that fails... 109 | try { 110 | fail_next_allocation{}, TestType{raw_ptr}; 111 | } catch (const std::bad_alloc&) { 112 | // If it does throw, good. Else, ignore it. 113 | } 114 | #endif 115 | } 116 | } 117 | 118 | CHECK_NO_LEAKS; 119 | } 120 | } 121 | 122 | TEMPLATE_LIST_TEST_CASE( 123 | "owner acquiring constructor bad alloc with deleter", "[construction][owner]", owner_types) { 124 | if constexpr (!must_use_make_observable && has_stateful_deleter) { 125 | volatile memory_tracker mem_track; 126 | 127 | { 128 | auto* raw_ptr = make_instance(); 129 | auto deleter = make_deleter_instance_1(); 130 | if constexpr (eoft_allocates) { 131 | fail_next_allocation{}, TestType{raw_ptr, deleter}; 132 | } else { 133 | #if !defined(OUP_COMPILER_LLVM) || !defined(NDEBUG) 134 | REQUIRE_THROWS_AS( 135 | (fail_next_allocation{}, TestType{raw_ptr, deleter}), std::bad_alloc); 136 | #else 137 | // LLVM in Release mode is able to inline the allocation, bypassing our 138 | // custom allocator that fails... 139 | try { 140 | fail_next_allocation{}, TestType{raw_ptr, deleter}; 141 | } catch (const std::bad_alloc&) { 142 | // If it does throw, good. Else, ignore it. 143 | } 144 | #endif 145 | } 146 | } 147 | 148 | CHECK_NO_LEAKS; 149 | } 150 | } 151 | 152 | TEMPLATE_LIST_TEST_CASE("owner acquiring constructor null", "[construction][owner]", owner_types) { 153 | if constexpr (!must_use_make_observable) { 154 | volatile memory_tracker mem_track; 155 | 156 | { 157 | TestType ptr(static_cast*>(nullptr)); 158 | 159 | CHECK(ptr.get() == nullptr); 160 | if constexpr (has_stateful_deleter) { 161 | CHECK(ptr.get_deleter().state_ == test_deleter::state::default_init); 162 | } 163 | CHECK_INSTANCES(0, 1); 164 | } 165 | 166 | CHECK_NO_LEAKS; 167 | } 168 | } 169 | 170 | TEMPLATE_LIST_TEST_CASE( 171 | "owner implicit conversion constructor", "[construction][owner]", owner_types) { 172 | if constexpr (has_base) { 173 | volatile memory_tracker mem_track; 174 | 175 | { 176 | TestType ptr_orig = make_pointer_deleter_1(); 177 | { 178 | base_ptr ptr(std::move(ptr_orig)); 179 | 180 | CHECK(ptr.get() != nullptr); 181 | if constexpr (has_stateful_deleter) { 182 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 183 | } 184 | CHECK_INSTANCES_DERIVED(1, 1, 2); 185 | } 186 | 187 | CHECK(ptr_orig.get() == nullptr); 188 | CHECK_INSTANCES_DERIVED(0, 0, 1); 189 | } 190 | 191 | CHECK_NO_LEAKS; 192 | } 193 | } 194 | 195 | TEMPLATE_LIST_TEST_CASE( 196 | "owner explicit conversion constructor", "[construction][owner]", owner_types) { 197 | if constexpr (has_base) { 198 | volatile memory_tracker mem_track; 199 | 200 | { 201 | base_ptr ptr_orig = make_pointer_deleter_1(); 202 | { 203 | TestType ptr( 204 | std::move(ptr_orig), dynamic_cast*>(ptr_orig.get())); 205 | 206 | CHECK(ptr.get() != nullptr); 207 | if constexpr (has_stateful_deleter) { 208 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 209 | } 210 | CHECK_INSTANCES_DERIVED(1, 1, 2); 211 | } 212 | 213 | CHECK(ptr_orig.get() == nullptr); 214 | CHECK_INSTANCES_DERIVED(0, 0, 1); 215 | } 216 | 217 | CHECK_NO_LEAKS; 218 | } 219 | } 220 | 221 | TEMPLATE_LIST_TEST_CASE( 222 | "owner explicit conversion constructor with custom deleter", 223 | "[construction][owner]", 224 | owner_types) { 225 | if constexpr (has_base && has_stateful_deleter) { 226 | volatile memory_tracker mem_track; 227 | 228 | { 229 | base_ptr ptr_orig = make_pointer_deleter_1(); 230 | { 231 | TestType ptr( 232 | std::move(ptr_orig), dynamic_cast*>(ptr_orig.get()), 233 | make_deleter_instance_2()); 234 | 235 | CHECK(ptr.get() != nullptr); 236 | if constexpr (has_stateful_deleter) { 237 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_2); 238 | } 239 | CHECK_INSTANCES_DERIVED(1, 1, 2); 240 | } 241 | 242 | CHECK(ptr_orig.get() == nullptr); 243 | CHECK_INSTANCES_DERIVED(0, 0, 1); 244 | } 245 | 246 | CHECK_NO_LEAKS; 247 | } 248 | } 249 | 250 | TEMPLATE_LIST_TEST_CASE( 251 | "owner explicit conversion constructor with null", "[construction][owner]", owner_types) { 252 | if constexpr (has_base) { 253 | volatile memory_tracker mem_track; 254 | 255 | { 256 | base_ptr ptr_orig = make_pointer_deleter_1(); 257 | { 258 | TestType ptr(std::move(ptr_orig), static_cast*>(nullptr)); 259 | 260 | CHECK(ptr.get() == nullptr); 261 | if constexpr (has_stateful_deleter) { 262 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 263 | } 264 | CHECK_INSTANCES_DERIVED(0, 0, 2); 265 | } 266 | 267 | CHECK(ptr_orig.get() == nullptr); 268 | CHECK_INSTANCES_DERIVED(0, 0, 1); 269 | } 270 | 271 | CHECK_NO_LEAKS; 272 | } 273 | } 274 | 275 | TEMPLATE_LIST_TEST_CASE( 276 | "owner explicit conversion constructor with custom deleter with null", 277 | "[construction][owner]", 278 | owner_types) { 279 | if constexpr (has_base && has_stateful_deleter) { 280 | volatile memory_tracker mem_track; 281 | 282 | { 283 | base_ptr ptr_orig = make_pointer_deleter_1(); 284 | { 285 | TestType ptr( 286 | std::move(ptr_orig), static_cast*>(nullptr), 287 | make_deleter_instance_2()); 288 | 289 | CHECK(ptr.get() == nullptr); 290 | if constexpr (has_stateful_deleter) { 291 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_2); 292 | } 293 | CHECK_INSTANCES_DERIVED(0, 0, 2); 294 | } 295 | 296 | CHECK(ptr_orig.get() == nullptr); 297 | CHECK_INSTANCES_DERIVED(0, 0, 1); 298 | } 299 | 300 | CHECK_NO_LEAKS; 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /tests/runtime_tests_owner_misc.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | #include "testing.hpp" 3 | 4 | TEMPLATE_LIST_TEST_CASE("owner size", "[size][owner]", owner_types) { 5 | using deleter_type = get_deleter; 6 | 7 | constexpr auto round_up = [](std::size_t i, std::size_t m) { 8 | return i % m == 0 ? i : i + m - i % m; 9 | }; 10 | 11 | // The deleter should have no overhead when stateless. 12 | // Otherwise, the overhead should be exactly the size of the deleter, modulo alignment. 13 | constexpr std::size_t deleter_overhead = 14 | std::is_empty_v 15 | ? 0 16 | : round_up(sizeof(deleter_type), std::max(alignof(deleter_type), alignof(void*))); 17 | 18 | CHECK(sizeof(TestType) == 2 * sizeof(void*) + deleter_overhead); 19 | } 20 | 21 | TEMPLATE_LIST_TEST_CASE("owner reset to null", "[reset][owner]", owner_types) { 22 | volatile memory_tracker mem_track; 23 | 24 | { 25 | TestType ptr = make_pointer_deleter_1(); 26 | ptr.reset(); 27 | 28 | CHECK(ptr.get() == nullptr); 29 | if constexpr (has_stateful_deleter) { 30 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 31 | } 32 | CHECK_INSTANCES(0, 1); 33 | } 34 | 35 | CHECK_NO_LEAKS; 36 | } 37 | 38 | TEMPLATE_LIST_TEST_CASE("owner reset to new", "[reset][owner]", owner_types) { 39 | if constexpr (!must_use_make_observable) { 40 | volatile memory_tracker mem_track; 41 | 42 | { 43 | TestType ptr = make_pointer_deleter_1(); 44 | auto* raw_ptr_orig = ptr.get(); 45 | ptr.reset(make_instance()); 46 | 47 | CHECK(ptr.get() != nullptr); 48 | CHECK(ptr.get() != raw_ptr_orig); 49 | if constexpr (has_stateful_deleter) { 50 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 51 | } 52 | CHECK_INSTANCES(1, 1); 53 | } 54 | 55 | CHECK_NO_LEAKS; 56 | } 57 | } 58 | 59 | TEMPLATE_LIST_TEST_CASE("owner reset to new bad alloc", "[reset][owner]", owner_types) { 60 | if constexpr (!must_use_make_observable) { 61 | volatile memory_tracker mem_track; 62 | 63 | { 64 | auto* raw_ptr1 = make_instance(); 65 | auto* raw_ptr2 = make_instance(); 66 | TestType ptr(raw_ptr1); 67 | bool has_thrown = false; 68 | try { 69 | force_next_allocation_failure = true; 70 | ptr.reset(raw_ptr2); 71 | force_next_allocation_failure = false; 72 | } catch (const std::bad_alloc&) { 73 | has_thrown = true; 74 | } 75 | 76 | if constexpr (eoft_allocates) { 77 | CHECK(!has_thrown); 78 | } else { 79 | CHECK(has_thrown); 80 | } 81 | 82 | if (has_thrown) { 83 | CHECK(ptr.get() != raw_ptr2); 84 | CHECK(ptr.get() == raw_ptr1); 85 | } else { 86 | CHECK(ptr.get() != raw_ptr1); 87 | CHECK(ptr.get() == raw_ptr2); 88 | } 89 | CHECK_INSTANCES(1, 1); 90 | } 91 | 92 | CHECK_NO_LEAKS; 93 | } 94 | } 95 | 96 | TEMPLATE_LIST_TEST_CASE("owner swap empty vs empty", "[swap][owner]", owner_types) { 97 | volatile memory_tracker mem_track; 98 | 99 | { 100 | TestType ptr1 = make_empty_pointer_deleter_1(); 101 | TestType ptr2 = make_empty_pointer_deleter_2(); 102 | ptr2.swap(ptr1); 103 | 104 | CHECK(ptr1.get() == nullptr); 105 | CHECK(ptr2.get() == nullptr); 106 | if constexpr (has_stateful_deleter) { 107 | CHECK(ptr1.get_deleter().state_ == test_deleter::state::special_init_2); 108 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 109 | } 110 | CHECK_INSTANCES(0, 2); 111 | } 112 | 113 | CHECK_NO_LEAKS; 114 | } 115 | 116 | TEMPLATE_LIST_TEST_CASE("owner swap valid vs empty", "[swap][owner]", owner_types) { 117 | volatile memory_tracker mem_track; 118 | 119 | { 120 | TestType ptr1 = make_pointer_deleter_1(); 121 | TestType ptr2 = make_empty_pointer_deleter_2(); 122 | ptr2.swap(ptr1); 123 | 124 | CHECK(ptr1.get() == nullptr); 125 | CHECK(ptr2.get() != nullptr); 126 | if constexpr (has_stateful_deleter) { 127 | CHECK(ptr1.get_deleter().state_ == test_deleter::state::special_init_2); 128 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 129 | } 130 | CHECK_INSTANCES(1, 2); 131 | } 132 | 133 | CHECK_NO_LEAKS; 134 | } 135 | 136 | TEMPLATE_LIST_TEST_CASE("owner swap empty vs valid", "[swap][owner]", owner_types) { 137 | volatile memory_tracker mem_track; 138 | 139 | { 140 | TestType ptr1 = make_empty_pointer_deleter_1(); 141 | TestType ptr2 = make_pointer_deleter_2(); 142 | ptr2.swap(ptr1); 143 | 144 | CHECK(ptr1.get() != nullptr); 145 | CHECK(ptr2.get() == nullptr); 146 | if constexpr (has_stateful_deleter) { 147 | CHECK(ptr1.get_deleter().state_ == test_deleter::state::special_init_2); 148 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 149 | } 150 | CHECK_INSTANCES(1, 2); 151 | } 152 | 153 | CHECK_NO_LEAKS; 154 | } 155 | 156 | TEMPLATE_LIST_TEST_CASE("owner swap valid vs valid", "[swap][owner]", owner_types) { 157 | volatile memory_tracker mem_track; 158 | 159 | { 160 | TestType ptr1 = make_pointer_deleter_1(); 161 | TestType ptr2 = make_pointer_deleter_2(); 162 | auto* raw_ptr1 = ptr1.get(); 163 | auto* raw_ptr2 = ptr2.get(); 164 | ptr2.swap(ptr1); 165 | 166 | CHECK(ptr1.get() != raw_ptr1); 167 | CHECK(ptr1.get() == raw_ptr2); 168 | CHECK(ptr2.get() != raw_ptr2); 169 | CHECK(ptr2.get() == raw_ptr1); 170 | if constexpr (has_stateful_deleter) { 171 | CHECK(ptr1.get_deleter().state_ == test_deleter::state::special_init_2); 172 | CHECK(ptr2.get_deleter().state_ == test_deleter::state::special_init_1); 173 | } 174 | CHECK_INSTANCES(2, 2); 175 | } 176 | 177 | CHECK_NO_LEAKS; 178 | } 179 | 180 | TEMPLATE_LIST_TEST_CASE("owner swap self vs self empty", "[swap][owner]", owner_types) { 181 | volatile memory_tracker mem_track; 182 | 183 | { 184 | TestType ptr = make_empty_pointer_deleter_1(); 185 | ptr.swap(ptr); 186 | 187 | CHECK(ptr.get() == nullptr); 188 | if constexpr (has_stateful_deleter) { 189 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 190 | } 191 | CHECK_INSTANCES(0, 1); 192 | } 193 | 194 | CHECK_NO_LEAKS; 195 | } 196 | 197 | TEMPLATE_LIST_TEST_CASE("owner swap self vs self valid", "[swap][owner]", owner_types) { 198 | volatile memory_tracker mem_track; 199 | 200 | { 201 | TestType ptr = make_pointer_deleter_1(); 202 | ptr.swap(ptr); 203 | 204 | CHECK(ptr.get() != nullptr); 205 | if constexpr (has_stateful_deleter) { 206 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 207 | } 208 | CHECK_INSTANCES(1, 1); 209 | } 210 | 211 | CHECK_NO_LEAKS; 212 | } 213 | 214 | TEMPLATE_LIST_TEST_CASE("owner dereference valid", "[dereference][owner]", owner_types) { 215 | volatile memory_tracker mem_track; 216 | 217 | { 218 | TestType ptr = make_pointer_deleter_1(); 219 | 220 | CHECK(ptr->state_ == test_object::state::default_init); 221 | CHECK((*ptr).state_ == test_object::state::default_init); 222 | } 223 | 224 | CHECK_NO_LEAKS; 225 | } 226 | 227 | TEMPLATE_LIST_TEST_CASE("owner get valid", "[get][owner]", owner_types) { 228 | volatile memory_tracker mem_track; 229 | 230 | { 231 | TestType ptr = make_pointer_deleter_1(); 232 | 233 | CHECK(ptr.get() != nullptr); 234 | CHECK(ptr.get()->state_ == test_object::state::default_init); 235 | } 236 | 237 | CHECK_NO_LEAKS; 238 | } 239 | 240 | TEMPLATE_LIST_TEST_CASE("owner get empty", "[get][owner]", owner_types) { 241 | volatile memory_tracker mem_track; 242 | 243 | { 244 | TestType ptr = make_empty_pointer_deleter_1(); 245 | 246 | CHECK(ptr.get() == nullptr); 247 | } 248 | 249 | CHECK_NO_LEAKS; 250 | } 251 | 252 | TEMPLATE_LIST_TEST_CASE("owner operator bool valid", "[bool][owner]", owner_types) { 253 | volatile memory_tracker mem_track; 254 | 255 | { 256 | TestType ptr = make_pointer_deleter_1(); 257 | 258 | if (ptr) { 259 | } else { 260 | FAIL("if (ptr) should have been true"); 261 | } 262 | } 263 | 264 | CHECK_NO_LEAKS; 265 | } 266 | 267 | TEMPLATE_LIST_TEST_CASE("owner operator bool empty", "[bool][owner]", owner_types) { 268 | volatile memory_tracker mem_track; 269 | 270 | { 271 | TestType ptr = make_empty_pointer_deleter_1(); 272 | 273 | if (ptr) { 274 | FAIL("if (ptr) should have been false"); 275 | } 276 | } 277 | 278 | CHECK_NO_LEAKS; 279 | } 280 | 281 | TEMPLATE_LIST_TEST_CASE("owner release valid", "[release][owner]", owner_types) { 282 | if constexpr (!is_sealed) { 283 | volatile memory_tracker mem_track; 284 | 285 | { 286 | TestType ptr = make_pointer_deleter_1(); 287 | auto* ptr_raw = ptr.get(); 288 | auto* ptr_released = ptr.release(); 289 | 290 | CHECK(ptr_released == ptr_raw); 291 | CHECK(ptr.get() == nullptr); 292 | if constexpr (has_stateful_deleter) { 293 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 294 | } 295 | CHECK_INSTANCES(1, 1); 296 | 297 | delete ptr_released; 298 | } 299 | 300 | CHECK_NO_LEAKS; 301 | } 302 | } 303 | 304 | TEMPLATE_LIST_TEST_CASE("owner release empty", "[release][owner]", owner_types) { 305 | if constexpr (!is_sealed) { 306 | volatile memory_tracker mem_track; 307 | 308 | { 309 | TestType ptr = make_empty_pointer_deleter_1(); 310 | auto* ptr_released = ptr.release(); 311 | 312 | CHECK(ptr_released == nullptr); 313 | CHECK(ptr.get() == nullptr); 314 | if constexpr (has_stateful_deleter) { 315 | CHECK(ptr.get_deleter().state_ == test_deleter::state::special_init_1); 316 | } 317 | CHECK_INSTANCES(0, 1); 318 | } 319 | 320 | CHECK_NO_LEAKS; 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /tests/size_benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "memory_tracker.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main() { 8 | memory_tracking = true; 9 | std::size_t init_alloc = 0u; 10 | 11 | using test_type = int; 12 | 13 | std::size_t unique_size = 0u; 14 | init_alloc = size_allocations; 15 | { 16 | std::unique_ptr ptr(new test_type); 17 | unique_size = size_allocations - sizeof(test_type) - init_alloc; 18 | std::cout << "unique_ptr size: " << sizeof(ptr) << ", " << unique_size << std::endl; 19 | } 20 | 21 | init_alloc = size_allocations; 22 | { 23 | std::unique_ptr ptr(new test_type); 24 | test_type* wptr = ptr.get(); 25 | std::cout << "raw pointer size: " << sizeof(wptr) << ", " 26 | << size_allocations - sizeof(test_type) - init_alloc - unique_size << std::endl; 27 | } 28 | 29 | std::size_t shared_size = 0u; 30 | init_alloc = size_allocations; 31 | { 32 | std::shared_ptr ptr(new test_type); 33 | shared_size = size_allocations - sizeof(test_type) - init_alloc; 34 | std::cout << "shared_ptr size: " << sizeof(ptr) << ", " << shared_size << std::endl; 35 | } 36 | 37 | init_alloc = size_allocations; 38 | { 39 | std::shared_ptr ptr(new test_type); 40 | std::weak_ptr wptr(ptr); 41 | std::cout << "weak_ptr size: " << sizeof(wptr) << ", " 42 | << size_allocations - sizeof(test_type) - init_alloc - shared_size << std::endl; 43 | } 44 | 45 | init_alloc = size_allocations; 46 | { 47 | std::shared_ptr ptr = std::make_shared(); 48 | shared_size = size_allocations - sizeof(test_type) - init_alloc; 49 | std::cout << "shared_ptr size (make_shared): " << sizeof(ptr) << ", " << shared_size 50 | << std::endl; 51 | } 52 | 53 | init_alloc = size_allocations; 54 | { 55 | std::shared_ptr ptr = std::make_shared(); 56 | std::weak_ptr wptr(ptr); 57 | std::cout << "weak_ptr size (make_shared): " << sizeof(wptr) << ", " 58 | << size_allocations - sizeof(test_type) - init_alloc - shared_size << std::endl; 59 | } 60 | 61 | std::size_t observable_size = 0u; 62 | init_alloc = size_allocations; 63 | { 64 | oup::observable_unique_ptr ptr(new test_type); 65 | observable_size = size_allocations - sizeof(test_type) - init_alloc; 66 | std::cout << "observable_unique_ptr size: " << sizeof(ptr) << ", " << observable_size 67 | << std::endl; 68 | } 69 | 70 | init_alloc = size_allocations; 71 | { 72 | oup::observable_unique_ptr ptr(new test_type); 73 | oup::observer_ptr wptr(ptr); 74 | std::cout << "observer_ptr size (unique): " << sizeof(wptr) << ", " 75 | << size_allocations - sizeof(test_type) - init_alloc - observable_size 76 | << std::endl; 77 | } 78 | 79 | init_alloc = size_allocations; 80 | { 81 | oup::observable_sealed_ptr ptr = oup::make_observable_sealed(); 82 | observable_size = size_allocations - sizeof(test_type) - init_alloc; 83 | std::cout << "observable_sealed_ptr size: " << sizeof(ptr) << ", " << observable_size 84 | << std::endl; 85 | } 86 | 87 | init_alloc = size_allocations; 88 | { 89 | oup::observable_sealed_ptr ptr = oup::make_observable_sealed(); 90 | oup::observer_ptr wptr(ptr); 91 | std::cout << "observer_ptr size (sealed): " << sizeof(wptr) << ", " 92 | << size_allocations - sizeof(test_type) - init_alloc - observable_size 93 | << std::endl; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/speed_benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "speed_benchmark_common.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | template 11 | auto run_benchmark_for(F&& func) { 12 | B bench{}; 13 | 14 | double elapsed = 0.0; 15 | double elapsed_square = 0.0; 16 | double attempts = 0.0; 17 | constexpr std::size_t num_iter = 1'000'000; 18 | constexpr double min_time = 0.2; 19 | 20 | while (elapsed * num_iter < min_time) { 21 | auto prev = timer::now(); 22 | 23 | for (std::size_t i = 0; i < num_iter; ++i) { 24 | func(bench); 25 | } 26 | 27 | auto now = timer::now(); 28 | 29 | double spent = 30 | std::chrono::duration_cast>(now - prev).count() / 31 | num_iter; 32 | elapsed += spent; 33 | elapsed_square += spent * spent; 34 | attempts += 1.0; 35 | } 36 | 37 | double stddev = 38 | std::sqrt(elapsed_square / attempts - (elapsed / attempts) * (elapsed / attempts)) / 39 | std::sqrt(attempts); 40 | 41 | return std::make_pair(elapsed / attempts, stddev); 42 | } 43 | 44 | template 45 | auto run_benchmark(F&& func) { 46 | using ref_type = benchmark>; 47 | 48 | auto result = run_benchmark_for(func); 49 | auto result_ref = run_benchmark_for(func); 50 | 51 | double ratio = result.first / result_ref.first; 52 | double rel_err = result.second / result.first; 53 | double rel_err_ref = result_ref.second / result_ref.first; 54 | double ratio_stddev = std::sqrt(rel_err * rel_err + rel_err_ref * rel_err_ref) * ratio; 55 | 56 | return std::make_pair(result, std::make_pair(ratio, ratio_stddev)); 57 | } 58 | 59 | std::unordered_map>> results; 60 | 61 | template 62 | struct get_type_name; 63 | 64 | template 65 | struct get_type_name> { 66 | static constexpr const char* value = "weak/shared"; 67 | }; 68 | 69 | template 70 | struct get_type_name> { 71 | static constexpr const char* value = "observer/obs_unique"; 72 | }; 73 | 74 | template 75 | struct get_type_name> { 76 | static constexpr const char* value = "observer/obs_sealed"; 77 | }; 78 | 79 | template 80 | void do_report(const char* name, const R& which) { 81 | std::cout << " - " << name << ": " << which.first.first * 1e6 << " +/- " 82 | << which.first.second * 1e6 << "us " 83 | << "(x" << which.second.first << " +/- " << which.second.second << ")" << std::endl; 84 | 85 | results[name][get_type_name::value].push_back(which.second.first); 86 | } 87 | 88 | double median(std::vector v) { 89 | const auto n = static_cast(v.size() / 2); 90 | std::nth_element(v.begin(), v.begin() + n, v.end()); 91 | return *(v.begin() + n); 92 | } 93 | 94 | std::string round1(double v) { 95 | std::ostringstream str; 96 | str << std::fixed << std::setprecision(1); 97 | str << std::round(v * 10.0) / 10.0; 98 | 99 | auto res = str.str(); 100 | if (res.find_first_of('.') == std::string::npos) { 101 | res += ".0"; 102 | } 103 | 104 | return res; 105 | } 106 | 107 | template 108 | void do_benchmarks_for_ptr(const char* type_name, const char* ptr_name) { 109 | using B = benchmark; 110 | 111 | auto construct_destruct_owner_empty = 112 | run_benchmark([](auto& b) { return b.construct_destruct_owner_empty(); }); 113 | auto construct_destruct_owner = 114 | run_benchmark([](auto& b) { return b.construct_destruct_owner(); }); 115 | auto construct_destruct_owner_factory = 116 | run_benchmark([](auto& b) { return b.construct_destruct_owner_factory(); }); 117 | auto dereference_owner = run_benchmark([](auto& b) { return b.dereference_owner(); }); 118 | auto construct_destruct_weak_empty = 119 | run_benchmark([](auto& b) { return b.construct_destruct_weak_empty(); }); 120 | auto construct_destruct_weak = 121 | run_benchmark([](auto& b) { return b.construct_destruct_weak(); }); 122 | auto construct_destruct_weak_copy = 123 | run_benchmark([](auto& b) { return b.construct_destruct_weak_copy(); }); 124 | auto dereference_weak = run_benchmark([](auto& b) { return b.dereference_weak(); }); 125 | 126 | std::cout << ptr_name << "<" << type_name << ">:" << std::endl; 127 | 128 | #define report(which) do_report(#which, which) 129 | report(construct_destruct_owner_empty); 130 | report(construct_destruct_owner); 131 | report(construct_destruct_owner_factory); 132 | report(dereference_owner); 133 | report(construct_destruct_weak_empty); 134 | report(construct_destruct_weak); 135 | report(construct_destruct_weak_copy); 136 | report(dereference_weak); 137 | #undef report 138 | 139 | std::cout << std::endl; 140 | } 141 | 142 | template 143 | void do_benchmarks(const char* type_name) { 144 | do_benchmarks_for_ptr>(type_name, "shared_ptr"); 145 | do_benchmarks_for_ptr>(type_name, "observable_unique_ptr"); 146 | do_benchmarks_for_ptr>(type_name, "observable_sealed_ptr"); 147 | } 148 | 149 | int main() { 150 | do_benchmarks("int"); 151 | do_benchmarks("float"); 152 | do_benchmarks("string"); 153 | do_benchmarks>("big_array"); 154 | 155 | std::vector> rows = { 156 | {"Create owner empty", "construct_destruct_owner_empty"}, 157 | {"Create owner", "construct_destruct_owner"}, 158 | {"Create owner factory", "construct_destruct_owner_factory"}, 159 | {"Dereference owner", "dereference_owner"}, 160 | {"Create observer empty", "construct_destruct_weak_empty"}, 161 | {"Create observer", "construct_destruct_weak"}, 162 | {"Create observer copy", "construct_destruct_weak_copy"}, 163 | {"Dereference observer", "dereference_weak"}, 164 | }; 165 | 166 | std::vector cols = {"weak/shared", "observer/obs_unique", "observer/obs_sealed"}; 167 | 168 | std::cout << "| Pointer | raw/unique | "; 169 | for (const auto& t : cols) { 170 | std::cout << t << " | "; 171 | } 172 | std::cout << std::endl; 173 | 174 | std::cout << "|---|---|"; 175 | for (const auto& t [[maybe_unused]] : cols) { 176 | std::cout << "---|"; 177 | } 178 | std::cout << std::endl; 179 | 180 | for (const auto& r : rows) { 181 | std::cout << "| " << r.first << " | 1 | "; 182 | for (const auto& t : cols) { 183 | if (r.second == "construct_destruct_owner" && t == "observer/obs_sealed") { 184 | std::cout << "N/A | "; 185 | } else { 186 | std::cout << round1(median(results[r.second][t])) << " | "; 187 | } 188 | } 189 | std::cout << std::endl; 190 | } 191 | 192 | return 0; 193 | } 194 | -------------------------------------------------------------------------------- /tests/speed_benchmark_common.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // External functions, the compiler cannot see through. Prevents optimisations. 9 | template 10 | void use_object(T&) noexcept; 11 | 12 | template 13 | struct pointer_traits; 14 | 15 | template 16 | struct pointer_traits> { 17 | using element_type = T; 18 | using ptr_type = std::unique_ptr; 19 | using weak_type = T*; 20 | 21 | static ptr_type make_ptr() noexcept { 22 | return ptr_type(new element_type); 23 | } 24 | static ptr_type make_ptr_factory() noexcept { 25 | return std::make_unique(); 26 | } 27 | static weak_type make_weak(ptr_type& p) noexcept { 28 | return p.get(); 29 | } 30 | template 31 | static void deref_weak(weak_type& p, F&& func) noexcept { 32 | return func(*p); 33 | } 34 | }; 35 | 36 | template 37 | struct pointer_traits> { 38 | using element_type = T; 39 | using ptr_type = std::shared_ptr; 40 | using weak_type = std::weak_ptr; 41 | 42 | static ptr_type make_ptr() noexcept { 43 | return ptr_type(new element_type); 44 | } 45 | static ptr_type make_ptr_factory() noexcept { 46 | return std::make_shared(); 47 | } 48 | static weak_type make_weak(ptr_type& p) noexcept { 49 | return weak_type(p); 50 | } 51 | template 52 | static void deref_weak(weak_type& p, F&& func) noexcept { 53 | if (auto s = p.lock()) 54 | func(*s); 55 | } 56 | }; 57 | 58 | template 59 | struct pointer_traits> { 60 | using element_type = T; 61 | using ptr_type = oup::observable_unique_ptr; 62 | using weak_type = oup::observer_ptr; 63 | 64 | static ptr_type make_ptr() noexcept { 65 | return ptr_type(new element_type); 66 | } 67 | static ptr_type make_ptr_factory() noexcept { 68 | return oup::make_observable_unique(); 69 | } 70 | static weak_type make_weak(ptr_type& p) noexcept { 71 | return weak_type(p); 72 | } 73 | template 74 | static void deref_weak(weak_type& p, F&& func) noexcept { 75 | return func(*p); 76 | } 77 | }; 78 | 79 | template 80 | struct pointer_traits> { 81 | using element_type = T; 82 | using ptr_type = oup::observable_sealed_ptr; 83 | using weak_type = oup::observer_ptr; 84 | 85 | static ptr_type make_ptr() noexcept { 86 | return oup::make_observable_sealed(); 87 | } 88 | static ptr_type make_ptr_factory() noexcept { 89 | return oup::make_observable_sealed(); 90 | } 91 | static weak_type make_weak(ptr_type& p) noexcept { 92 | return weak_type(p); 93 | } 94 | template 95 | static void deref_weak(weak_type& p, F&& func) noexcept { 96 | return func(*p); 97 | } 98 | }; 99 | 100 | template 101 | struct benchmark { 102 | using traits = pointer_traits; 103 | using element_type = typename traits::element_type; 104 | using owner_type = typename traits::ptr_type; 105 | using weak_type = typename traits::weak_type; 106 | 107 | owner_type owner; 108 | weak_type weak; 109 | 110 | benchmark() : owner(traits::make_ptr()), weak(traits::make_weak(owner)) {} 111 | 112 | void construct_destruct_owner_empty(); 113 | 114 | void construct_destruct_owner(); 115 | 116 | void construct_destruct_owner_factory(); 117 | 118 | void construct_destruct_weak_empty(); 119 | 120 | void construct_destruct_weak(); 121 | 122 | void construct_destruct_weak_copy(); 123 | 124 | void dereference_owner(); 125 | 126 | void dereference_weak(); 127 | }; 128 | 129 | using timer = std::chrono::high_resolution_clock; 130 | -------------------------------------------------------------------------------- /tests/speed_benchmark_utility.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | template 7 | void use_object(T&) noexcept {} 8 | 9 | template void use_object(int&) noexcept; 10 | template void use_object(float&) noexcept; 11 | template void use_object(std::string&) noexcept; 12 | template void use_object>(std::array&) noexcept; 13 | 14 | template void use_object(int*&) noexcept; 15 | template void use_object(float*&) noexcept; 16 | template void use_object(std::string*&) noexcept; 17 | template void use_object*>(std::array*&) noexcept; 18 | 19 | template void use_object>(std::unique_ptr&) noexcept; 20 | template void use_object>(std::unique_ptr&) noexcept; 21 | template void use_object>(std::unique_ptr&) noexcept; 22 | template void use_object>>( 23 | std::unique_ptr>&) noexcept; 24 | 25 | template void use_object>(std::shared_ptr&) noexcept; 26 | template void use_object>(std::shared_ptr&) noexcept; 27 | template void use_object>(std::shared_ptr&) noexcept; 28 | template void use_object>>( 29 | std::shared_ptr>&) noexcept; 30 | 31 | template void use_object>(std::weak_ptr&) noexcept; 32 | template void use_object>(std::weak_ptr&) noexcept; 33 | template void use_object>(std::weak_ptr&) noexcept; 34 | template void use_object>>( 35 | std::weak_ptr>&) noexcept; 36 | 37 | template void 38 | use_object>(oup::observable_unique_ptr&) noexcept; 39 | template void 40 | use_object>(oup::observable_unique_ptr&) noexcept; 41 | template void use_object>( 42 | oup::observable_unique_ptr&) noexcept; 43 | template void use_object>>( 44 | oup::observable_unique_ptr>&) noexcept; 45 | 46 | template void 47 | use_object>(oup::observable_sealed_ptr&) noexcept; 48 | template void 49 | use_object>(oup::observable_sealed_ptr&) noexcept; 50 | template void use_object>( 51 | oup::observable_sealed_ptr&) noexcept; 52 | template void use_object>>( 53 | oup::observable_sealed_ptr>&) noexcept; 54 | 55 | template void use_object>(oup::observer_ptr&) noexcept; 56 | template void use_object>(oup::observer_ptr&) noexcept; 57 | template void use_object>(oup::observer_ptr&) noexcept; 58 | template void use_object>>( 59 | oup::observer_ptr>&) noexcept; 60 | -------------------------------------------------------------------------------- /tests/speed_benchmark_utility2.cpp: -------------------------------------------------------------------------------- 1 | #include "speed_benchmark_common.hpp" 2 | 3 | template 4 | void benchmark::construct_destruct_owner_empty() { 5 | auto p = owner_type{}; 6 | use_object(p); 7 | } 8 | 9 | template 10 | void benchmark::construct_destruct_owner() { 11 | auto p = traits::make_ptr(); 12 | use_object(p); 13 | } 14 | 15 | template 16 | void benchmark::construct_destruct_owner_factory() { 17 | auto p = traits::make_ptr_factory(); 18 | use_object(p); 19 | } 20 | 21 | template 22 | void benchmark::construct_destruct_weak_empty() { 23 | auto p = weak_type{}; 24 | use_object(p); 25 | } 26 | 27 | template 28 | void benchmark::construct_destruct_weak() { 29 | auto wp = traits::make_weak(owner); 30 | use_object(wp); 31 | } 32 | 33 | template 34 | void benchmark::construct_destruct_weak_copy() { 35 | auto wp = weak; 36 | use_object(wp); 37 | } 38 | 39 | template 40 | void benchmark::dereference_owner() { 41 | use_object(*owner); 42 | } 43 | 44 | template 45 | void benchmark::dereference_weak() { 46 | traits::deref_weak(weak, [](auto& o) { use_object(o); }); 47 | } 48 | 49 | template struct benchmark>; 50 | template struct benchmark>; 51 | template struct benchmark>; 52 | template struct benchmark>>; 53 | 54 | template struct benchmark>; 55 | template struct benchmark>; 56 | template struct benchmark>; 57 | template struct benchmark>>; 58 | 59 | template struct benchmark>; 60 | template struct benchmark>; 61 | template struct benchmark>; 62 | template struct benchmark>>; 63 | 64 | template struct benchmark>; 65 | template struct benchmark>; 66 | template struct benchmark>; 67 | template struct benchmark>>; 68 | -------------------------------------------------------------------------------- /tests/testing.hpp: -------------------------------------------------------------------------------- 1 | #include "snitch/snitch_macros_check.hpp" 2 | #include "snitch/snitch_macros_exceptions.hpp" 3 | #include "snitch/snitch_macros_test_case.hpp" 4 | #include "tests_common.hpp" 5 | 6 | // clang-format off 7 | using owner_types = snitch::type_list< 8 | oup::observable_unique_ptr, 9 | oup::observable_sealed_ptr, 10 | oup::observable_unique_ptr, 11 | oup::observable_sealed_ptr, 12 | oup::observable_unique_ptr, 13 | oup::observable_sealed_ptr, 14 | oup::observable_unique_ptr, 15 | oup::observable_unique_ptr, 16 | oup::observable_unique_ptr, 17 | oup::observable_sealed_ptr, 18 | oup::basic_observable_ptr, 19 | oup::basic_observable_ptr, 20 | oup::basic_observable_ptr, 21 | oup::observable_unique_ptr, 22 | oup::observable_sealed_ptr, 23 | oup::observable_unique_ptr, 24 | oup::observable_sealed_ptr, 25 | oup::observable_unique_ptr, 26 | oup::observable_sealed_ptr, 27 | oup::observable_unique_ptr, 28 | oup::observable_sealed_ptr, 29 | oup::observable_unique_ptr, 30 | oup::observable_sealed_ptr, 31 | oup::observable_unique_ptr, 32 | oup::observable_sealed_ptr 33 | >; 34 | // clang-format on 35 | 36 | #define CHECK_INSTANCES(TEST_OBJECTS, TEST_DELETER) \ 37 | do { \ 38 | CHECK(instances == (TEST_OBJECTS)); \ 39 | if constexpr (has_stateful_deleter) { \ 40 | CHECK(instances_deleter == (TEST_DELETER)); \ 41 | } \ 42 | } while (0) 43 | 44 | #define CHECK_INSTANCES_DERIVED(TEST_OBJECTS, TEST_DERIVED, TEST_DELETER) \ 45 | do { \ 46 | CHECK(instances == (TEST_OBJECTS)); \ 47 | CHECK(instances_derived == (TEST_DERIVED)); \ 48 | if constexpr (has_stateful_deleter) { \ 49 | CHECK(instances_deleter == (TEST_DELETER)); \ 50 | } \ 51 | } while (0) 52 | 53 | #define CHECK_NO_LEAKS \ 54 | do { \ 55 | CHECK_INSTANCES_DERIVED(0, 0, 0); \ 56 | CHECK(mem_track.allocated() == 0u); \ 57 | CHECK(mem_track.double_delete() == 0u); \ 58 | } while (0) 59 | 60 | #if defined(NDEBUG) 61 | // When not in debug (hence, assuming optimisations are turned on), 62 | // some compilers manage to optimise-out some heap allocations, so use a looser 63 | // check. 64 | # define CHECK_MAX_ALLOC(MAX_ALLOC) CHECK(mem_track.allocated() <= MAX_ALLOC) 65 | #else 66 | // In debug, allocations must be exactly as expected. 67 | # define CHECK_MAX_ALLOC(MAX_ALLOC) CHECK(mem_track.allocated() == MAX_ALLOC) 68 | #endif 69 | 70 | // clang-format off 71 | #if defined(__clang__) 72 | # define SNITCH_WARNING_DISABLE_SELF_ASSIGN _Pragma("clang diagnostic ignored \"-Wself-assign-overloaded\"") 73 | #elif defined(__GNUC__) 74 | # define SNITCH_WARNING_DISABLE_SELF_ASSIGN do {} while (0) 75 | #elif defined(_MSC_VER) 76 | # define SNITCH_WARNING_DISABLE_SELF_ASSIGN do {} while (0) 77 | #else 78 | # define SNITCH_WARNING_DISABLE_SELF_ASSIGN do {} while (0) 79 | #endif 80 | // clang-format on 81 | -------------------------------------------------------------------------------- /tests/tests_common.cpp: -------------------------------------------------------------------------------- 1 | #include "tests_common.hpp" 2 | 3 | #include 4 | 5 | int instances = 0; 6 | int instances_derived = 0; 7 | int instances_deleter = 0; 8 | 9 | bool next_test_object_constructor_throws = false; 10 | bool next_test_object_constructor_calls_observer_from_this = false; 11 | 12 | constexpr bool debug_instances = false; 13 | 14 | test_object::test_object() { 15 | if (next_test_object_constructor_throws) { 16 | next_test_object_constructor_throws = false; 17 | 18 | if constexpr (debug_instances) { 19 | std::printf("%p creation throws\n", static_cast(this)); 20 | } 21 | 22 | throw throw_constructor{}; 23 | } 24 | 25 | if constexpr (debug_instances) { 26 | std::printf("%p created\n", static_cast(this)); 27 | } 28 | 29 | ++instances; 30 | } 31 | 32 | test_object::test_object(state s) : state_(s) { 33 | if (next_test_object_constructor_throws) { 34 | next_test_object_constructor_throws = false; 35 | 36 | if constexpr (debug_instances) { 37 | std::printf("%p creation throws\n", static_cast(this)); 38 | } 39 | 40 | throw throw_constructor{}; 41 | } 42 | 43 | if constexpr (debug_instances) { 44 | std::printf("%p created\n", static_cast(this)); 45 | } 46 | 47 | ++instances; 48 | } 49 | 50 | test_object::~test_object() noexcept { 51 | if constexpr (debug_instances) { 52 | std::printf("%p deleted\n", static_cast(this)); 53 | } 54 | 55 | --instances; 56 | } 57 | 58 | test_object_derived::test_object_derived() { 59 | ++instances_derived; 60 | } 61 | 62 | test_object_derived::test_object_derived(state s) : test_object(s) { 63 | ++instances_derived; 64 | } 65 | 66 | test_object_derived::~test_object_derived() noexcept { 67 | --instances_derived; 68 | } 69 | 70 | test_deleter::test_deleter() noexcept { 71 | ++instances_deleter; 72 | } 73 | 74 | test_deleter::test_deleter(state s) noexcept : state_(s) { 75 | ++instances_deleter; 76 | } 77 | 78 | test_deleter::test_deleter(const test_deleter& source) noexcept : state_(source.state_) { 79 | ++instances_deleter; 80 | } 81 | 82 | test_deleter::test_deleter(test_deleter&& source) noexcept : state_(source.state_) { 83 | source.state_ = state::empty; 84 | ++instances_deleter; 85 | } 86 | 87 | test_deleter::~test_deleter() noexcept { 88 | --instances_deleter; 89 | } 90 | 91 | test_deleter& test_deleter::operator=(test_deleter&& source) noexcept { 92 | state_ = source.state_; 93 | source.state_ = state::empty; 94 | return *this; 95 | } 96 | 97 | void test_deleter::operator()(test_object* ptr) noexcept { 98 | delete ptr; 99 | } 100 | 101 | void test_deleter::operator()(const test_object* ptr) noexcept { 102 | delete ptr; 103 | } 104 | 105 | void test_deleter::operator()(std::nullptr_t) noexcept {} 106 | --------------------------------------------------------------------------------