├── .gitattributes ├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── VERSION ├── cmake ├── install.cmake ├── isptr-config.cmake.in └── isptr.pc.in ├── doc ├── intrusive_shared_ptr.md ├── ref_counted.md ├── refcnt_ptr.md ├── reference_counting.md └── trivial_abi.md ├── inc └── intrusive_shared_ptr │ ├── apple_cf_ptr.h │ ├── com_ptr.h │ ├── common.h │ ├── intrusive_shared_ptr.h │ ├── python_ptr.h │ ├── ref_counted.h │ └── refcnt_ptr.h ├── modules └── isptr.cppm ├── test ├── CMakeLists.txt ├── mocks.h ├── test_abstract_ref_counted.cpp ├── test_abstract_ref_counted_st.cpp ├── test_apple_cf_ptr.cpp ├── test_atomic.cpp ├── test_com_ptr.cpp ├── test_delegating_traits.cpp ├── test_general.cpp ├── test_main.cpp ├── test_out_ptr.cpp ├── test_python_ptr.cpp ├── test_ref_counted.cpp ├── test_ref_counted_st.cpp ├── test_weak_ref_counted.cpp └── test_weak_ref_counted_st.cpp └── tools ├── create-release ├── make-module └── module-template.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | doc/* linguist-documentation 2 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | main: 10 | runs-on: ubuntu-latest 11 | permissions: write-all 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Get release Name 17 | shell: python 18 | run: | 19 | import os 20 | ref = os.environ['GITHUB_REF'] 21 | name = ref[ref.rfind('/') + 2:] 22 | with open(os.environ['GITHUB_ENV'], 'w') as env: 23 | print('RELEASE_NAME=' + name, file=env) 24 | 25 | - name: Zip Headers 26 | shell: bash 27 | run: | 28 | tar -czf intrusive_shared_ptr-${{ env.RELEASE_NAME }}.tar.gz \ 29 | --transform 's/inc/intrusive_shared_ptr\/inc/' \ 30 | --transform 's/modules/intrusive_shared_ptr\/modules/' \ 31 | inc modules 32 | 33 | - name: Make release 34 | uses: softprops/action-gh-release@v2 35 | id: create_release 36 | with: 37 | draft: true 38 | prerelease: false 39 | name: ${{ env.RELEASE_NAME }} 40 | body: ...edit me... 41 | files: | 42 | intrusive_shared_ptr-${{ env.RELEASE_NAME }}.tar.gz 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'README.md' 7 | - '.gitignore' 8 | - 'LICENSE' 9 | - 'CHANGELOG.md' 10 | - 'doc/**' 11 | - 'tools/**' 12 | 13 | jobs: 14 | build: 15 | runs-on: ${{ matrix.os }} 16 | name: ${{ matrix.name }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | include: 21 | - { os: macos-latest, name: 'macOS latest' } 22 | 23 | - { os: windows-latest, module: true, name: 'Windows latest' } 24 | 25 | - { os: ubuntu-latest, compiler: gcc, version: 11, name: 'GCC 11' } 26 | - { os: ubuntu-latest, compiler: gcc, version: 12, name: 'GCC 12' } 27 | - { os: ubuntu-latest, compiler: gcc, version: 13, name: 'GCC 13' } 28 | - { os: ubuntu-24.04, compiler: gcc, version: 14, module: true, name: 'GCC 14' } 29 | 30 | - { os: ubuntu-22.04, compiler: clang, version: 13, name: 'Clang 13' } 31 | - { os: ubuntu-22.04, compiler: clang ,version: 14, name: 'Clang 14' } 32 | - { os: ubuntu-22.04, compiler: clang, version: 15, name: 'Clang 15' } 33 | - { os: ubuntu-22.04, compiler: clang, version: 16, name: 'Clang 16' } 34 | - { os: ubuntu-latest, compiler: clang, version: 17, name: 'Clang 17' } 35 | - { os: ubuntu-latest, compiler: clang, version: 18, module: true, name: 'Clang 13' } 36 | - { os: ubuntu-latest, compiler: clang, version: 19, module: true, name: 'Clang 19' } 37 | - { os: ubuntu-latest, compiler: clang, version: 20, module: true, name: 'Clang 20' } 38 | 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v4 42 | 43 | - name: Setup Python 44 | uses: actions/setup-python@v5 45 | with: 46 | python-version: "3.12" 47 | 48 | - name: System Setup 49 | shell: bash 50 | run: | 51 | if [[ '${{ matrix.os }}' == ubuntu-* ]]; then 52 | if [[ '${{ matrix.compiler }}' == 'clang' ]]; then 53 | wget https://apt.llvm.org/llvm.sh 54 | chmod u+x llvm.sh 55 | sudo ./llvm.sh ${{ matrix.version }} 56 | sudo apt-get install -y clang-tools-${{ matrix.version }} 57 | echo "CC=clang-${{ matrix.version }}" >> $GITHUB_ENV 58 | echo "CXX=clang++-${{ matrix.version }}" >> $GITHUB_ENV 59 | fi 60 | 61 | if [[ '${{ matrix.compiler }}' == 'gcc' ]]; then 62 | sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 63 | sudo apt-get update 64 | sudo apt-get install -y gcc-${{ matrix.version }} g++-${{ matrix.version }} 65 | echo "CC=gcc-${{ matrix.version }}" >> $GITHUB_ENV 66 | echo "CXX=g++-${{ matrix.version }}" >> $GITHUB_ENV 67 | fi 68 | 69 | mkdir bin 70 | wget -qO- https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip | \ 71 | gunzip > bin/ninja 72 | chmod a+x bin/ninja 73 | echo PATH=`pwd`/bin:$PATH >> $GITHUB_ENV 74 | echo "CMAKE_GENERATOR=-GNinja" >> $GITHUB_ENV 75 | fi 76 | 77 | if [[ '${{ matrix.module}}' == 'true' ]]; then 78 | echo "CMAKE_ARGS=-DISPTR_ENABLE_MODULE=ON" >> $GITHUB_ENV 79 | fi 80 | 81 | - name: Configure 82 | shell: bash 83 | run: | 84 | cmake $CMAKE_GENERATOR -S . -B build $CMAKE_ARGS -DISPTR_ENABLE_PYTHON=ON -DCMAKE_BUILD_TYPE=Release 85 | 86 | - name: Build and Test 87 | shell: bash 88 | run: | 89 | cmake --build build --config Release --target run-test 90 | 91 | container: 92 | runs-on: ubuntu-latest 93 | name: ${{ matrix.name }} 94 | container: ${{ matrix.container }} 95 | strategy: 96 | fail-fast: false 97 | matrix: 98 | include: 99 | - {container: gcc:15.1, module: true, name: 'GCC 15'} 100 | 101 | steps: 102 | - name: Checkout 103 | uses: actions/checkout@v4 104 | 105 | - name: System Setup 106 | shell: bash 107 | run: | 108 | apt-get update 109 | apt-get install -y python3-dev 110 | 111 | export CMAKE_VERSION=3.28.6 112 | wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-Linux-x86_64.sh \ 113 | -q -O /tmp/cmake-install.sh \ 114 | && chmod u+x /tmp/cmake-install.sh \ 115 | && /tmp/cmake-install.sh --skip-license --prefix=/usr \ 116 | rm -f /tmp/cmake-install.sh 117 | 118 | wget -qO- https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip | \ 119 | gunzip > /usr/bin/ninja 120 | chmod a+x /usr/bin/ninja 121 | echo "CMAKE_GENERATOR=-GNinja" >> $GITHUB_ENV 122 | 123 | if [[ '${{ matrix.module}}' == 'true' ]]; then 124 | echo "CMAKE_ARGS=-DISPTR_ENABLE_MODULE=ON" >> $GITHUB_ENV 125 | fi 126 | 127 | - name: Configure 128 | shell: bash 129 | run: | 130 | cmake $CMAKE_GENERATOR -S . -B build $CMAKE_ARGS -DISPTR_ENABLE_PYTHON=ON -DCMAKE_BUILD_TYPE=Release 131 | 132 | - name: Build and Test 133 | shell: bash 134 | run: | 135 | cmake --build build --config Release --target run-test 136 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | .vs 4 | /build/ 5 | /**/out/ 6 | 7 | *.pdb 8 | CMakeSettings.json 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | 6 | ## Unreleased 7 | 8 | ## [1.9] - 2025-05-11 9 | 10 | ## Fixed 11 | - Restored support for pre-3.28 CMake. Module functionality will not be available under those 12 | - GCC15 error (declaration of partial specialization in unbraced export-declaration) when using module 13 | 14 | ## [1.8] - 2025-04-14 15 | 16 | ### Added 17 | - Support for `std::format` (if available). Instances of `intrusive_shared_ptr` can be passed to `std::format` 18 | and will be printed like a `void *`. 19 | - Support for `std::hash`. 20 | - `get_inout_param()` method. This provides the same functionality as `std::inout_ptr` but is available for pre-C++23 compilation. 21 | 22 | ### Changed 23 | - Performance improvements for `std::out_ptr` etc. 24 | - C++ module is no longer using CMake library mechanism. See [README](README.md) for details on how to use module. 25 | 26 | ### Fixed 27 | - Some internal helpers moved into `internal` namespace where they always should have been 28 | - Internal macros are now properly cleared at the end of the header files 29 | 30 | ## [1.7] - 2025-04-09 31 | 32 | ### Added 33 | - Full support for `std::out_ptr` and `std::inout_ptr` when provided by the standard library. 34 | Older `get_output_param()` continues to work and its semantics have been aligned with `std::out_ptr` 35 | behavior. 36 | 37 | ### Changed 38 | - C++ module CMake targets now include standard version suffixes. Instead of `isptrm::isptrm` you need 39 | to use `isptrm-20::isptrm-20` or `isptrm-23::isptrm-23` to match the version of C++ your client code 40 | is compiled under. (Apparently you cannot use a module built with a different C++ version from the 41 | client code. Sigh.) 42 | 43 | ## [1.6] - 2025-02-11 44 | 45 | ### Changed 46 | - Test targets are now excluded from default build 47 | - Doctest is now included via `` to make it possible to build 48 | tests against an external Doctest not brought via FetchContent. 49 | 50 | ### Fixed 51 | - `common.h` header is now correctly installed by CMake install 52 | 53 | ## [1.5] - 2024-08-16 54 | 55 | ### Added 56 | - C++ module support (experimental). The library can now be used as C++ module. See [README](https://github.com/gershnik/intrusive_shared_ptr/) for details. 57 | 58 | ### Fixed 59 | - `weak_reference::single_threaded` flag is made `constexpr` rather than `static const` 60 | 61 | ## [1.4] - 2023-07-22 62 | 63 | ### Changed 64 | - Updated CMake configuration to modernize it and allow local installation 65 | 66 | ## [1.3] - 2023-03-31 67 | 68 | ### Added 69 | - Added single threaded mode support to `ref_counted` 70 | 71 | ### Changed 72 | - Updated documentation 73 | 74 | ## [1.2] - 2023-03-17 75 | 76 | ### Added 77 | - Pre-defined specialization for Python object and type pointer for use in Python extensions. See python_ptr.h header for details. 78 | 79 | 80 | ## [1.1] - 2022-06-09 81 | 82 | ### Changed 83 | - CMake configuration modified to mark this library sources as PRIVATE rather than INTERFACE. This plays nicer with IDEs and avoid polluting library clients with its headers 84 | 85 | ## [1.0] - 2022-05-22 86 | 87 | ### Added 88 | - First release 89 | 90 | [1.0]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.0 91 | [1.1]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.1 92 | [1.2]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.2 93 | [1.3]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.3 94 | [1.4]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.4 95 | [1.5]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.5 96 | [1.6]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.6 97 | [1.7]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.7 98 | [1.8]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.8 99 | [1.9]: https://github.com/gershnik/intrusive_shared_ptr/releases/v1.9 100 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2004 Eugene Gershnik 3 | # 4 | # Use of this source code is governed by a BSD-style 5 | # license that can be found in the LICENSE file or at 6 | # https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | # 8 | 9 | cmake_minimum_required(VERSION 3.25) 10 | 11 | file(READ VERSION ISPTR_VERSION) 12 | if (NOT ISPTR_VERSION) 13 | message(FATAL_ERROR "Cannot determine library version (VERSION file not found)") 14 | endif() 15 | string(STRIP ${ISPTR_VERSION} ISPTR_VERSION) 16 | 17 | project(isptr VERSION ${ISPTR_VERSION} LANGUAGES CXX) 18 | 19 | set(SRCDIR ${CMAKE_CURRENT_LIST_DIR}) 20 | set(LIBNAME isptr) 21 | 22 | add_library(${LIBNAME} INTERFACE) 23 | 24 | target_include_directories(${LIBNAME} 25 | INTERFACE 26 | $ 27 | $ # /include 28 | ) 29 | 30 | set(PUBLIC_HEADERS 31 | ${SRCDIR}/inc/intrusive_shared_ptr/intrusive_shared_ptr.h 32 | ${SRCDIR}/inc/intrusive_shared_ptr/common.h 33 | ${SRCDIR}/inc/intrusive_shared_ptr/apple_cf_ptr.h 34 | ${SRCDIR}/inc/intrusive_shared_ptr/com_ptr.h 35 | ${SRCDIR}/inc/intrusive_shared_ptr/python_ptr.h 36 | ${SRCDIR}/inc/intrusive_shared_ptr/refcnt_ptr.h 37 | ${SRCDIR}/inc/intrusive_shared_ptr/ref_counted.h 38 | ) 39 | 40 | target_sources(${LIBNAME} 41 | INTERFACE 42 | FILE_SET HEADERS BASE_DIRS ${SRCDIR}/inc FILES 43 | ${PUBLIC_HEADERS} 44 | PRIVATE 45 | ${PUBLIC_HEADERS} 46 | ) 47 | 48 | add_library(intrusive-shared-ptr ALIAS ${LIBNAME}) 49 | add_library(${LIBNAME}::${LIBNAME} ALIAS ${LIBNAME}) 50 | 51 | set(ISPTR_MODULES_BASE_DIRS ${SRCDIR}/modules) 52 | set(ISPTR_MODULES_FILES ${SRCDIR}/modules/isptr.cppm) 53 | 54 | if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.28") 55 | 56 | function(isptr_add_module target_name visibility) 57 | target_sources(${target_name} ${visibility} 58 | FILE_SET isptr_module TYPE CXX_MODULES BASE_DIRS ${ISPTR_MODULES_BASE_DIRS} FILES ${ISPTR_MODULES_FILES} 59 | ) 60 | endfunction() 61 | 62 | endif() 63 | 64 | if (PROJECT_IS_TOP_LEVEL) 65 | 66 | include(cmake/install.cmake) 67 | 68 | add_subdirectory(test) 69 | 70 | endif() -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2004 Eugene Gershnik 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 | # Intro 2 | This is yet another implementation of an intrusive [reference counting](http://en.wikipedia.org/wiki/Reference_counting) 3 | [smart pointer](http://en.wikipedia.org/wiki/Smart_pointer), highly configurable reference counted base class and adapters. 4 | 5 | The code requires C++17 or above compiler. 6 | 7 | It is known to work with:
8 | Xcode 11 or above
9 | Microsoft Visual Studio 2019 or above
10 | Clang 8 or above
11 | GCC 7.4.0 or above
12 | 13 | It can be used either as a classical header-only library or as C++ module (experimental). 14 | 15 | Documentation and formal tests are work in progress. 16 | 17 | 18 | 19 | - [Why bother?](#why-bother) 20 | - [Named conversions from raw pointers](#named-conversions-from-raw-pointers) 21 | - [No ADL](#no-adl) 22 | - [Support for output parameters](#support-for-output-parameters) 23 | - [Support for operator->*](#support-for-operator-) 24 | - [Atomic access](#atomic-access) 25 | - [Trivial ABI](#trivial-abi) 26 | - [Correct implementation of a "reference counted base" class](#correct-implementation-of-a-reference-counted-base-class) 27 | - [Support for weak pointers](#support-for-weak-pointers) 28 | - [Integration](#integration) 29 | - [CMake via FetchContent](#cmake-via-fetchcontent) 30 | - [Vcpkg](#vcpkg) 31 | - [Platform package managers](#platform-package-managers) 32 | - [Building and installing on your system](#building-and-installing-on-your-system) 33 | - [Basic use](#basic-use) 34 | - [CMake package](#cmake-package) 35 | - [Via pkg-config](#via-pkg-config) 36 | - [Copying to your sources](#copying-to-your-sources) 37 | - [Usage](#usage) 38 | - [Using provided base classes](#using-provided-base-classes) 39 | - [Supporting weak pointers](#supporting-weak-pointers) 40 | - [Using with Apple CoreFoundation types](#using-with-apple-corefoundation-types) 41 | - [Using with Microsoft COM interfaces](#using-with-microsoft-com-interfaces) 42 | - [Using with Python objects](#using-with-python-objects) 43 | - [Using with non-reference counted types](#using-with-non-reference-counted-types) 44 | - [Atomic operations](#atomic-operations) 45 | - [Constexpr functionality](#constexpr-functionality) 46 | - [Module support](#module-support) 47 | - [Reference](#reference) 48 | 49 | 50 | 51 | ## Why bother? 52 | There are multiple other intrusive smart pointers available including one from [Boost](https://www.boost.org/doc/libs/1_71_0/libs/smart_ptr/doc/html/smart_ptr.html#intrusive_ptr) 53 | and nowadays there is even a [proposal](http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0468r0.html) 54 | to add one to the standard C++ library, so why create another one? 55 | Unfortunately, as far as I can tell, all existing implementations, and that includes the standard library proposal at the time 56 | of this writing, suffer from numerous deficiencies that make them hard or annoying to use in real life code. 57 | The most serious problems addressed here are as follows 58 | 59 | ### Named conversions from raw pointers 60 | All other libraries offer a conversion in the form 61 | `smart_ptr(T * p);` 62 | In my opinion, this is an extremely bad idea. When looking at a call like `smart_ptr(foo())` can you quickly tell whether this adds a reference count or "attaches" the smart pointer to a raw one? That's right, you cannot! The answer depends 63 | on the smart pointer implementation or even on specific traits used. This makes the behavior invisible and hard to predict at the **call site**. It guarantees that someone, somewhere will make a wrong assumption. In my experience, almost all reference counting bugs happen on the **boundary** between code that uses smart and raw pointers where such conversions are abundant. 64 | Just like any form of dangerous cast this one has to be **explicit** in calling code. As an aside, ObjectiveC ARC did it right 65 | with their explicit and visible `__bridge` casts between raw and smart pointers. 66 | Note that having a boolean argument (like what Boost and many other implementations do) in constructor isn't a solution. 67 | Can you quickly tell what `smart_ptr(p, true)` does? Is it "true, add reference" or "true, copy it"? 68 | 69 | This library uses named functions to perform conversion. You see exactly what is being done at the call site. 70 | 71 | ### No ADL 72 | 73 | Many libraries use [ADL](https://en.wikipedia.org/wiki/Argument-dependent_name_lookup) to find "add reference" and 74 | "release reference" functions for the underlying type. 75 | That is, they have expressions like `add_ref(p)` in their implementation, and expect a function named `add_ref` that accepts pointer to the underlying type is supposed to be found via ADL. 76 | This solution is great in many cases but it breaks when working with some C types like Apple's `CTypeRef`. This one is actually a typedef to `void *` so if you have an `add_ref` that accepts it, you have just made every unrelated `void *` reference counted (with very bad results if you accidentally put a wrong object into a smart pointer). 77 | A better way to define how reference counting is done is to pass a traits class to the smart pointer. (The standard library proposal gets this one right). 78 | 79 | This library uses traits. 80 | 81 | ### Support for output parameters 82 | 83 | Often times you need to pass smart pointer as an output parameter to a C function that takes `T **` 84 | Many other smart pointers either 85 | - ignore this scenario, requiring you to introduce extra raw pointer and unsafe code, or 86 | - overload `operator&` which is a horrendously bad idea (it breaks lots of generic code which assumes that `&foo` gives 87 | an address of foo, not something else) 88 | The right solution is to have a proxy class convertible to `T **`. 89 | 90 | Since C++23 the standard library provides `std::out_ptr` and `std::inout_ptr` to deal with this issue. This library 91 | fully supports those when they are available. 92 | 93 | When standard library support is not available, this library also exposes `get_output_param()` method that 94 | returns an inner proxy class (which behaves similarly to `std::out_ptr_t`) 95 | 96 | ### Support for `operator->*` 97 | 98 | This might seem to be a minor thing but is really annoying in generic code. For some reason no smart pointers bother to provide 99 | `operator->*` so that pointers to members could be accessed via the same syntax as for raw pointers. In non-generic code 100 | you can always work around it via `(*p).*whatever` but in generic code this is not an option. 101 | 102 | ### Atomic access 103 | 104 | Sometimes you need to operate on smart pointers atomically. To the best of my knowledge no library currently provides this functionality. 105 | 106 | This library provides a specialization of `std::atomic>` extending to it the normal `std::atomic` semantics. 107 | 108 | ### Trivial ABI 109 | 110 | When built with CLang compiler `intrusive_shared_ptr` is marked with [\[\[clang::trivial_abi\]\]](https://clang.llvm.org/docs/AttributeReference.html#trivial-abi) attribute. A good description of what this attribute does and why it is important 111 | for performance can be found [here](https://quuxplusone.github.io/blog/2018/05/02/trivial-abi-101/). 112 | Another take on the performance issue as a comment on standard library proposal can be found 113 | [here](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1351r0.html#params). 114 | [This page](doc/trivial_abi.md) contains details on why this is a good idea and why concerns about order of destruction do not really matter here. 115 | 116 | ### Correct implementation of a "reference counted base" class 117 | 118 | This is not directly a problem with smart pointers but with the base classes often provided together with them to implement an 119 | intrusively counted class. Very often they contain subtle bugs (see 120 | ['A note on implementing reference counted objects'](doc/reference_counting.md) for more details). It is also tricky to 121 | create a base class that can work well for different requirements without compromising efficiency. 122 | 123 | ### Support for weak pointers 124 | 125 | Continuing on the base class theme, when doing intrusive reference counting, supporting (or not) weak pointers is the responsibility of the counted class. Supporting weak pointers also usually involves tradeoffs in terms of performance or memory consumption. 126 | This library base class allows user to enable a decent implementation of weak pointers via policy based design. 127 | 128 | 129 | ## Integration 130 | 131 | ### CMake via FetchContent 132 | 133 | ```cmake 134 | include(FetchContent) 135 | ... 136 | 137 | FetchContent_Declare(isptr 138 | GIT_REPOSITORY https://github.com/gershnik/intrusive_shared_ptr.git 139 | GIT_TAG v1.8 #use the tag, branch or sha you need 140 | GIT_SHALLOW TRUE 141 | ) 142 | ... 143 | FetchContent_MakeAvailable(isptr) 144 | ... 145 | 146 | #To use header files: 147 | target_link_libraries(mytarget 148 | PRIVATE 149 | isptr::isptr 150 | ) 151 | 152 | #To use C++ module (the second param is the visibilty) 153 | isptr_add_module(mytarget PRIVATE) 154 | ``` 155 | > ℹ️ _[What is FetchContent?](https://cmake.org/cmake/help/latest/module/FetchContent.html)_ 156 | 157 | ### Vcpkg 158 | 159 | In classic mode, run the following vcpkg command: 160 | ```bash 161 | vcpkg install intrusive-shared-ptr 162 | ``` 163 | 164 | In manifest mode, run the following vcpkg command in your project directory: 165 | ```bash 166 | vcpkg add port intrusive-shared-ptr 167 | ``` 168 | 169 | ### Platform package managers 170 | 171 | On Debian based systems `intrusive-shared-ptr` might be available via APT. 172 | 173 | You can consult https://pkgs.org/search/?q=libisptr-dev for up-to-date availability information. 174 | 175 | If available, it can be installed via 176 | 177 | ```bash 178 | apt install libisptr-dev 179 | ``` 180 | 181 | ### Building and installing on your system 182 | 183 | You can also build and install this library on your system using CMake. 184 | 185 | 1. Download or clone this repository into SOME_PATH 186 | 2. On command line: 187 | ```bash 188 | cd SOME_PATH 189 | cmake -S . -B build 190 | 191 | #Optional 192 | #cmake --build build --target run-test 193 | 194 | #install to /usr/local 195 | sudo cmake --install build 196 | #or for a different prefix 197 | #cmake --install build --prefix /usr 198 | ``` 199 | 200 | Once the library has been installed it can be used int the following ways: 201 | 202 | #### Basic use 203 | 204 | To use the header files set the include directory to `/include` where `` 205 | is the install prefix from above. 206 | 207 | To use C++ module (if enabled during the build) include `/include/intrusive_shared_ptr/isptr.cppm` 208 | in your build. 209 | 210 | 211 | #### CMake package 212 | 213 | ```cmake 214 | find_package(isptr) 215 | 216 | #To use header files: 217 | target_link_libraries(mytarget 218 | PRIVATE 219 | isptr::isptr 220 | ) 221 | 222 | #To use C++ module (the second param is the visibilty) 223 | isptr_add_module(mytarget PRIVATE) 224 | ``` 225 | 226 | #### Via `pkg-config` 227 | 228 | Add the output of `pkg-config --cflags isptr` to your compiler flags. 229 | 230 | Note that the default installation prefix `/usr/local` might not be in the list of places your 231 | `pkg-config` looks into. If so you might need to do: 232 | ```bash 233 | export PKG_CONFIG_PATH=/usr/local/share/pkgconfig 234 | ``` 235 | before running `pkg-config` 236 | 237 | 238 | ### Copying to your sources 239 | 240 | You can also simply download this repository from [Releases](https://github.com/gershnik/intrusive_shared_ptr/releases) page 241 | (named `intrusive_shared_ptr-X.Y.tar.gz`) and unpack it somewhere in your source tree. 242 | 243 | To use header files add the `inc` sub-directory to your include path. 244 | 245 | To use the module add `modules/isptr.cppm` to your build. 246 | 247 | ## Usage 248 | 249 | All the types in this library are declared in `namespace isptr`. For brevity the namespace is omitted below. 250 | Add `isptr::` prefix to all the type or use `using` declaration in your own code. 251 | 252 | The header ``/module `isptr` provides a template 253 | 254 | ```cpp 255 | template 256 | class intrusive_shared_ptr; 257 | ``` 258 | 259 | Where `T` is the type of the pointee and `Traits` a class that should provide 2 static functions that look like this 260 | 261 | ```cpp 262 | static void add_ref(SomeType * ptr) noexcept 263 | { 264 | //increment reference count. ptr is guaranteed to be non-nullptr 265 | } 266 | static void sub_ref(SomeType * ptr) noexcept 267 | { 268 | //decrement reference count. ptr is guaranteed to be non-nullptr 269 | } 270 | ``` 271 | 272 | `SomeType *` should be a pointer type to which `T *` is convertible to. It is possible to make `add_ref` and `sub_ref` 273 | templates, if desired, though this is usually not necessary. 274 | 275 | 276 | To create `intrusive_shared_ptr` from a raw `T *` there are 2 functions: 277 | 278 | ```cpp 279 | //pass the smart pointer in without changing the reference count 280 | template 281 | intrusive_shared_ptr intrusive_shared_ptr::noref(T * p) noexcept; 282 | 283 | //adopt the pointer and bump the reference count 284 | template 285 | intrusive_shared_ptr intrusive_shared_ptr::ref(T * p) noexcept 286 | ``` 287 | 288 | It is possible to use `intrusive_shared_ptr` directly but the name is long and ugly so a better approach is to 289 | wrap in a typedef and wrapper functions like this 290 | 291 | ```cpp 292 | struct my_type 293 | {}; 294 | 295 | struct my_intrusive_traits 296 | { 297 | static void add_ref(my_type * ptr) noexcept; //implement 298 | static void sub_ref(my_type * ptr) noexcept; //implement 299 | }; 300 | 301 | template 302 | using my_ptr = intrusive_shared_ptr; 303 | 304 | template 305 | my_ptr my_retain_func(T * ptr) { 306 | return my_ptr::ref(ptr); 307 | } 308 | template 309 | my_ptr my_attach_func(T * ptr) { 310 | return my_ptr::noref(ptr); 311 | } 312 | 313 | ``` 314 | 315 | The library provides such wrappers for some common scenarios. If you fully control the definition of `my_type` then 316 | it is possible to simplify things even further with header `refcnt_ptr.h`. It adapts `intrusive_shared_ptr` to traits 317 | exposed as inner type `refcnt_ptr_traits`. You can use it like this: 318 | 319 | ```cpp 320 | #include 321 | //Or, if using modules: 322 | //import isptr; 323 | 324 | using namespace isptr; 325 | 326 | struct my_type 327 | { 328 | struct refcnt_ptr_traits 329 | { 330 | static void add_ref(my_type * ptr) noexcept; //implement 331 | static void sub_ref(my_type * ptr) noexcept; //implement 332 | }; 333 | }; 334 | 335 | //now you can use refcnt_ptr for the pointer type and refcnt_attach and refcnt_retain free functions e.g. 336 | 337 | //create from raw pointer (created with count 1) 338 | foo raw = new my_type(); 339 | refcnt_ptr p1 = refcnt_attach(raw); 340 | 341 | //create directly 342 | auto p1 = make_refcnt(); 343 | 344 | //assign from raw pointer bumping reference count 345 | refcnt_ptr p2; 346 | p2 = refcnt_retain(raw); 347 | 348 | 349 | ``` 350 | 351 | 352 | ### Using provided base classes 353 | 354 | To implement `my_type` above the library provides a base class you can inherit from which will do the right thing. 355 | 356 | ```cpp 357 | #include 358 | #include 359 | //Or, if using modules: 360 | //import isptr; 361 | 362 | using namespace isptr; 363 | 364 | class foo : ref_counted 365 | { 366 | friend ref_counted; 367 | public: 368 | void method(); 369 | private: 370 | ~foo() noexcept = default; //prevent manual deletion 371 | }; 372 | 373 | //you can use auto to declare p1, p2 and p3. The full type is spelled out for 374 | //demonstration purposes only 375 | 376 | //attach from raw pointer (created with count 1) 377 | refcnt_ptr p1 = refcnt_attach(new foo()); 378 | 379 | //create directly 380 | refcnt_ptr p2 = make_refcnt(); 381 | 382 | //assign from raw pointer bumping reference count 383 | foo * raw = ... 384 | refcnt_ptr p3 = refcnt_retain(raw); 385 | 386 | ``` 387 | 388 | The type of the reference count is `int` by default. If you need to you can customize it. 389 | 390 | ```cpp 391 | 392 | class tiny : ref_counted //use char as count type 393 | { 394 | friend ref_counted; 395 | 396 | char c; 397 | }; 398 | 399 | static_assert(sizeof(tiny) == 2); 400 | 401 | ``` 402 | 403 | More details can be found in [this document](doc/ref_counted.md) 404 | 405 | ### Supporting weak pointers 406 | 407 | If you want to support weak pointers you need to tell `ref_counted` about it. Since weak pointers include overhead 408 | even if you never create one by default they are disabled. 409 | 410 | ```cpp 411 | #include 412 | #include 413 | //Or, if using modules: 414 | //import isptr; 415 | 416 | using namespace isptr; 417 | 418 | class foo : weak_ref_counted //alias for ref_counted 419 | { 420 | void method(); 421 | }; 422 | 423 | refcnt_ptr p1 = refcnt_attach(new foo()); 424 | foo::weak_ptr w1 = p1->get_weak_ptr(); 425 | refcnt_ptr p2 = w1->lock(); 426 | ``` 427 | 428 | Note that you cannot customize the type of reference count if you support weak pointers - it will always be `intptr_t`. 429 | More details can be found in [this document](doc/ref_counted.md) 430 | 431 | ### Using with Apple CoreFoundation types 432 | 433 | ```cpp 434 | 435 | #include 436 | //Or, if using modules: 437 | //import isptr; 438 | 439 | using namespace isptr; 440 | 441 | //Use auto in real code. Type is spelled out for clarity 442 | cf_ptr str = cf_attach(CFStringCreateWithCString(nullptr, 443 | "Hello", 444 | kCFStringEncodingUTF8)); 445 | std::cout << CFStringGetLength(str.get()); 446 | 447 | CFArrayRef raw = ...; 448 | //Use auto in real code. 449 | cf_ptr array = cf_retain(raw); 450 | 451 | ``` 452 | 453 | ### Using with Microsoft COM interfaces 454 | 455 | ```cpp 456 | 457 | #include 458 | //Or, if using modules: 459 | //import isptr; 460 | 461 | using namespace isptr; 462 | 463 | com_shared_ptr pStream; 464 | //Before C++23 465 | CreateStreamOnHGlobal(nullptr, true, pStream.get_output_param()); 466 | //With C++23 and later 467 | CreateStreamOnHGlobal(nullptr, true, std::out_ptr(pStream)); 468 | pStream->Write(....); 469 | 470 | ``` 471 | 472 | ### Using with Python objects 473 | 474 | ```cpp 475 | #include 476 | //Or, if using modules: 477 | //import isptr; 478 | 479 | using namespace isptr; 480 | 481 | auto str = py_attach(PyUnicode_FromString("Hello")); 482 | std::cout << PyUnicode_GetLength(str.get()); 483 | 484 | ``` 485 | 486 | ### Using with non-reference counted types 487 | 488 | On occasion when you have a code that uses intrusive reference counting a lot you might need to handle a type 489 | which you cannot modify and which is not by itself reference counted. 490 | In such situation you can use an adapter (if you prefer derivation) or wrapper (if you prefer containment) that makes it such 491 | 492 | Adapter: 493 | ```cpp 494 | #include 495 | //Or, if using modules: 496 | //import isptr; 497 | 498 | using counted_map = ref_counted_adapter>; 499 | 500 | auto ptr = make_refcnt(); 501 | (*ptr)["abc"] = 7; 502 | std::cout << ptr->size(); 503 | 504 | using weakly_counted_map = weak_ref_counted_adapter>; 505 | 506 | auto ptr1 = make_refcnt(); 507 | (*ptr1)["abc"] = 7; 508 | std::cout << ptr1->size(); 509 | foo::weak_ptr w1 = p1->get_weak_ptr(); 510 | refcnt_ptr p2 = w1->lock(); 511 | ``` 512 | 513 | Wrapper: 514 | ```cpp 515 | #include 516 | //Or, if using modules: 517 | //import isptr; 518 | 519 | using counted_map = ref_counted_wrapper>; 520 | 521 | auto ptr = make_refcnt(); 522 | ptr->wrapped()["abc"] = 7; 523 | std::cout << ptr->wrapped().size(); 524 | 525 | using weakly_counted_map = weak_ref_counted_wrapper>; 526 | 527 | auto ptr1 = make_refcnt(); 528 | ptr1->wrapped()["abc"] = 7; 529 | std::cout << ptr1->wrapped().size(); 530 | foo::weak_ptr w1 = p1->get_weak_ptr(); 531 | refcnt_ptr p2 = w1->lock(); 532 | ``` 533 | 534 | ### Atomic operations 535 | 536 | The library provides a partial specialization 537 | 538 | ```cpp 539 | 540 | template 541 | std::atomic>; 542 | 543 | ``` 544 | 545 | which exposes normal `std::atomic` functionality. For example: 546 | 547 | ```cpp 548 | 549 | using my_ptr = intrusive_shared_ptr; 550 | 551 | using my_atomic_ptr = std::atomic; 552 | 553 | my_ptr ptr = ...; 554 | my_atomic_ptr aptr = ptr; 555 | 556 | ptr = aptr.load(); 557 | //or 558 | ptr = aptr; 559 | 560 | aptr.store(ptr); 561 | //or 562 | aptr = ptr; 563 | 564 | my_ptr ptr1 = aptr.exchange(ptr); 565 | 566 | //etc. 567 | 568 | ``` 569 | 570 | ## Constexpr functionality 571 | 572 | When built with C++20 compiler `intrusive_shared_ptr` is fully constexpr capable. You can do things like 573 | 574 | ```cpp 575 | 576 | using my_ptr = intrusive_shared_ptr; 577 | 578 | constexpr my_ptr foo; 579 | 580 | ``` 581 | 582 | Due to non-default destructors this functionality is not available on C++17 583 | 584 | ## Module support 585 | 586 | Since version 1.5 this library support being used as a C++ module. 587 | This mode is currently **experimental**. Please report bugs if you encounter any issues. 588 | 589 | In order to use C++ modules you need a compiler that supports them. 590 | Currently CLang >= 16 and MSVC toolset >= 14.34 are definitely known to work. 591 | Other compilers/versions may or may not work. 592 | 593 | If using CMake follow the requirements at [cmake-cxxmodules](https://cmake.org/cmake/help/latest/manual/cmake-cxxmodules.7.html). 594 | 595 | The library consists of a single module file at [modules/isptr.cppm](modules/isptr.cppm). 596 | This file is auto-generated from all the library headers. Include it in your build. 597 | 598 | ## Reference 599 | 600 | * [intrusive_shared_ptr.h](doc/intrusive_shared_ptr.md) 601 | * [refcnt_ptr.h](doc/refcnt_ptr.md) 602 | * [ref_counted.h](doc/ref_counted.md) 603 | 604 | 605 | 606 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.9 2 | -------------------------------------------------------------------------------- /cmake/install.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2004 Eugene Gershnik 3 | # 4 | # Use of this source code is governed by a BSD-style 5 | # license that can be found in the LICENSE file or at 6 | # https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | # 8 | 9 | include(GNUInstallDirs) 10 | include(CMakePackageConfigHelpers) 11 | 12 | install(TARGETS isptr EXPORT isptr FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 13 | install(EXPORT isptr NAMESPACE isptr:: FILE isptr-exports.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/isptr) 14 | 15 | install(FILES ${SRCDIR}/modules/isptr.cppm DESTINATION ${CMAKE_INSTALL_LIBDIR}/isptr) 16 | 17 | 18 | configure_package_config_file( 19 | ${CMAKE_CURRENT_LIST_DIR}/isptr-config.cmake.in 20 | ${CMAKE_CURRENT_BINARY_DIR}/isptr-config.cmake 21 | INSTALL_DESTINATION 22 | ${CMAKE_INSTALL_LIBDIR}/isptr 23 | ) 24 | 25 | write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/isptr-config-version.cmake 26 | COMPATIBILITY SameMajorVersion 27 | ARCH_INDEPENDENT 28 | ) 29 | 30 | install( 31 | FILES 32 | ${CMAKE_CURRENT_BINARY_DIR}/isptr-config.cmake 33 | ${CMAKE_CURRENT_BINARY_DIR}/isptr-config-version.cmake 34 | DESTINATION 35 | ${CMAKE_INSTALL_LIBDIR}/isptr 36 | ) 37 | 38 | file(RELATIVE_PATH FROM_PCFILEDIR_TO_PREFIX ${CMAKE_INSTALL_FULL_DATAROOTDIR}/isptr ${CMAKE_INSTALL_PREFIX}) 39 | string(REGEX REPLACE "/+$" "" FROM_PCFILEDIR_TO_PREFIX "${FROM_PCFILEDIR_TO_PREFIX}") 40 | 41 | configure_file( 42 | ${CMAKE_CURRENT_LIST_DIR}/isptr.pc.in 43 | ${CMAKE_CURRENT_BINARY_DIR}/isptr.pc 44 | @ONLY 45 | ) 46 | 47 | install( 48 | FILES 49 | ${CMAKE_CURRENT_BINARY_DIR}/isptr.pc 50 | DESTINATION 51 | ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig 52 | ) -------------------------------------------------------------------------------- /cmake/isptr-config.cmake.in: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2004 Eugene Gershnik 3 | # 4 | # Use of this source code is governed by a BSD-style 5 | # license that can be found in the LICENSE file or at 6 | # https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | # 8 | 9 | @PACKAGE_INIT@ 10 | 11 | 12 | # Avoid repeatedly including the targets 13 | if(NOT TARGET isptr::isptr) 14 | include(${CMAKE_CURRENT_LIST_DIR}/isptr-exports.cmake) 15 | 16 | get_filename_component(MYPATH "${CMAKE_CURRENT_LIST_FILE}" PATH) 17 | 18 | set(ISPTR_MODULES_BASE_DIRS "${MYPATH}") 19 | set(ISPTR_MODULES_FILES "${MYPATH}/isptr.cppm") 20 | 21 | function(isptr_add_module target_name visibility) 22 | target_sources(${target_name} ${visibility} 23 | FILE_SET isptr_module TYPE CXX_MODULES BASE_DIRS ${ISPTR_MODULES_BASE_DIRS} FILES ${ISPTR_MODULES_FILES} 24 | ) 25 | endfunction() 26 | 27 | set(MYPATH) 28 | 29 | endif() 30 | 31 | 32 | -------------------------------------------------------------------------------- /cmake/isptr.pc.in: -------------------------------------------------------------------------------- 1 | prefix=${pcfiledir}/@FROM_PCFILEDIR_TO_PREFIX@ 2 | exec_prefix=${prefix} 3 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 4 | 5 | Name: isptr 6 | Description: Yet another implementation of an intrusive reference counting for C++ 7 | Version: @PROJECT_VERSION@ 8 | Requires: 9 | Cflags: -I${includedir} 10 | Libs: 11 | -------------------------------------------------------------------------------- /doc/intrusive_shared_ptr.md: -------------------------------------------------------------------------------- 1 | # Header `` 2 | 3 | ## Class isptr::intrusive_shared_ptr 4 | 5 | ```cpp 6 | template 7 | class ISPTR_TRIVIAL_ABI intrusive_shared_ptr; 8 | ``` 9 | 10 | Macro `ISPTR_TRIVIAL_ABI` expands to `[[clang::trivial_abi]]` when compiled under clang. 11 | 12 | ### Traits requirements 13 | 14 | Traits must expose two static methods with the following signatures: 15 | 16 | ```cpp 17 | add_ref(T *) noexcept 18 | sub_ref(T *) noexcept 19 | ``` 20 | 21 | Return value of either method is ignored. The argument is never `nullptr`. 22 | 23 | ### Namespace Types 24 | 25 | - `template constexpr bool are_intrusive_shared_traits` checks whether given Traits satisfy the traits requirements above for the given type T. 26 | - `template using is_intrusive_shared_ptr = ...` type trait that is `std::true_type` or `std::false_type` depending on whether T is a valid `intrusive_shared_ptr` type. 27 | - `template bool constexpr is_intrusive_shared_ptr_v = is_intrusive_shared_ptr::value;` 28 | 29 | 30 | ### Nested Types 31 | 32 | ```cpp 33 | using pointer = T *; 34 | using element_type = T; 35 | using traits_type = Traits; 36 | ``` 37 | 38 | ### Operations 39 | 40 | #### Construction/assignment/destruction 41 | 42 | - Default constructor. `noexcept`, produces a null pointer 43 | - Constructor from `nullptr`. Same as above 44 | - Copy constructor. `noexcept`. Accepts: 45 | - Objects of the same type and traits 46 | - Objects that point to `Y` such as `Y *` is convertible to `T *` using the same or different traits. 47 | - Move constructor. `noexcept`. Accepts: 48 | - Objects of the same type and traits. No changes to reference count are performed. 49 | - Objects that point to `Y` such as `Y *` is convertible to `T *` using the same or different traits. 50 | If the source and destination traits are the same no changes to source reference count are performed. 51 | - Copy assignment. `noexcept`. Accepts: 52 | - Objects of the same type and traits 53 | - Objects that point to `Y` such as `Y *` is convertible to `T *` using the same or different traits. 54 | - Move assignment. `noexcept`. Accepts: 55 | - Objects of the same type and traits 56 | - Objects that point to `Y` such as `Y *` is convertible to `T *` using the same or different traits. 57 | If the source and destination traits are the same no changes to source reference count are performed. 58 | - Destructor. `noexcept`. Non-virtual. 59 | 60 | - `static intrusive_shared_ptr noref() noexcept` Creates a smart pointer from a raw pointer without modifying the reference count. 61 | - `static intrusive_shared_ptr ref() noexcept` Creates a smart pointer from a raw pointer and increments reference count. 62 | 63 | #### Instance methods 64 | 65 | - `T * get() const noexcept` Returns the stored pointer. Reference count is not modified. 66 | - `T * operator->() const noexcept` Returns the stored pointer. Reference count is not modified. 67 | - `T & operator*() const noexcept` Returns the reference to pointee. Undefined if stored pointer is nullptr 68 | - `template M & operator->*(M T::*memptr) const noexcept` Returns the result of `'stored pointer'->*memptr` 69 | This allows access via pointee's pointer to members 70 | - `explicit operator bool() const noexcept` true if the pointer is non null 71 | - Before C++23: `output_param get_output_param() noexcept` returns a temporary object that exposes `operator T**() && noexcept`. This can 72 | be passed as an output parameter to C functions that return a reference counted pointer. The destructor of the temporary 73 | will fill this pointer with the returned result. 74 | In C++23 and above use `std::out_ptr` or `std::inout_ptr`instead. 75 | - `T * release() noexcept` Releases the stored pointer. The object is set to null and calling code assumes ownership of the 76 | pointer. No adjustment is made to the reference count. 77 | - `void reset() noexcept` Clears the stored pointer. The reference count is decremented. 78 | - `void swap(intrusive_shared_ptr & other) noexcept` and
79 | `friend void swap(intrusive_shared_ptr & lhs, intrusive_shared_ptr & rhs) noexcept` (ADL only) 80 | Perform swap of pointers of the same type 81 | 82 | #### Namespace methods 83 | 84 | - Equality: `==`, `!=`. All `noexcept`. Work between `intrusive_shared_ptr` of compatible types and 85 | any traits as well as raw pointers and nullptr. 86 | - Comparisons: `<`, `<=`, `>=`, `>`. All `noexcept`. On C++20 these are replaced by `<=>`. Work between `intrusive_shared_ptr` of compatible types and 87 | any traits as well as raw pointers. 88 | - Hash code: `constexpr size_t hash_value(const intrusive_shared_ptr & ptr) noexcept`. 89 | - Stream output: `template friend std::basic_ostream & operator<<(std::basic_ostream & str, const intrusive_shared_ptr & ptr)` (ADL only) outputs the stored pointer value to a stream 90 | 91 | - `Dest intrusive_const_cast(intrusive_shared_ptr src) noexcept`. Performs an equivalent of `const_cast` on passed argument. The destination type needs to be a valid `intrusive_shared_ptr` type whose underlying type is convertible from source's via `const_cast`. 92 | - `Dest intrusive_static_cast(intrusive_shared_ptr src) noexcept`. Performs an equivalent of `static_cast` on passed argument. The destination type needs to be a valid `intrusive_shared_ptr` type whose underlying type is convertible from source's via `static_cast`. 93 | - `Dest intrusive_dynamic_cast(intrusive_shared_ptr src) noexcept`. Performs an equivalent of `dynamic_cast` on passed argument. The destination type needs to be a valid `intrusive_shared_ptr` type whose underlying type is convertible from source's via `dynamic_cast`. Returns a null result if underlying `dynamic_cast` fails. 94 | 95 | #### Specializations 96 | 97 | - `std::out_ptr_t` and `std::inout_ptr_t`. Provide support for using 98 | `std::out_ptr` and `std::inout_ptr` with `intrusive_shared_ptr` 99 | - `std::formatter`. Provides support for using `std::format` with `intrusive_shared_ptr` instances 100 | - `std::hash` 101 | 102 | ## Class std::atomic<isptr::intrusive_shared_ptr> 103 | 104 | Provides the standard set of `std::atomic` functionality: 105 | 106 | - `using value_type = isptr::intrusive_shared_ptr` 107 | - `static constexpr bool is_always_lock_free` 108 | - `constexpr atomic() noexcept`. Initializes with null pointer. 109 | - `atomic(value_type desired) noexcept` 110 | - `atomic(const atomic&) = delete` Deleted! 111 | - `void operator=(const atomic&) = delete` Deleted! 112 | - `~atomic() noexcept` 113 | - `void operator=(value_type desired) noexcept` 114 | - `operator value_type() const noexcept` 115 | - `value_type load(memory_order order = memory_order_seq_cst) const noexcept` 116 | - `void store(value_type desired, memory_order order = memory_order_seq_cst) noexcept` 117 | - `value_type exchange(value_type desired, memory_order order = memory_order_seq_cst) noexcept` 118 | - `bool compare_exchange_strong(value_type & expected, value_type desired, memory_order success, memory_order failure) noexcept` 119 | - `bool compare_exchange_strong(value_type & expected, value_type desired, memory_order order = memory_order_seq_cst) noexcept` 120 | - `bool compare_exchange_weak(value_type & expected, value_type desired, memory_order success, memory_order failure) noexcept` 121 | - `bool compare_exchange_weak(value_type & expected, value_type desired, memory_order order = memory_order_seq_cst) noexcept` 122 | - `bool is_lock_free() const noexcept` 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /doc/ref_counted.md: -------------------------------------------------------------------------------- 1 | # Header `` 2 | 3 | 4 | 5 | - [Class isptr::ref_counted](#class-isptrref_counted) 6 | - [Customization](#customization) 7 | - [Usage](#usage) 8 | - [Rules for constructors and destructors](#rules-for-constructors-and-destructors) 9 | - [Limitations](#limitations) 10 | - [Class isptr::weak_reference](#class-isptrweak_reference) 11 | - [Customization](#customization) 12 | - [Usage](#usage) 13 | - [Types](#types) 14 | - [Methods](#methods) 15 | - [Class isptr::ref_counted_adapter](#class-isptrref_counted_adapter) 16 | - [Methods](#methods) 17 | - [Class isptr::ref_counted_wrapper](#class-isptrref_counted_wrapper) 18 | - [Members](#members) 19 | - [Methods](#methods) 20 | 21 | 22 | 23 | ## Class isptr::ref_counted 24 | 25 | ```cpp 26 | template> 29 | class ref_counted; 30 | 31 | template 32 | using weak_ref_counted = ref_counted; 33 | ``` 34 | 35 | `ref_counted` is meant to be used as base class for any class you want to make reference counted. 36 | It is supposed to be used in conjunction with [`refcnt_ptr`](refcnt_ptr.md) specialization of `intrusive_shared_ptr` but, 37 | of course, can also be used via manual reference counting calls or other smart pointers if desired. 38 | 39 | It uses CRTP to access the derived class, avoiding the need for and overhead of virtual functions. 40 | Thus, the first template parameter: `Derived` must be the name of the derived class. Other template 41 | parameters have reasonable defaults and allow customization of reference counting functionality. 42 | These are described below. 43 | 44 | 45 | ### Customization 46 | 47 | `ref_counted` can be customized in the following ways: 48 | 49 | * Support for weak references. Default: no. Weak reference support adds some performance overhead even for object which never have weak reference taken so it is not enabled by default. In addition weak reference support requires the reference count type to be `intptr_t` which might be wasteful for many applications where `int` is sufficient. 50 | * Single-threaded mode. Default: no. In single threaded mode reference count manipulations are not thread safe so you cannot 51 | share `refcnt_ptr`s across threads. This results in increased performance though, and might be worthwhile in some scenarios. 52 | * Type of the reference count. Can only be customized if weak references are not supported. Default: `int`. If weak references are supported the count type is always `intptr_t`. You can reduce the size of derived classes by using a smaller type if it can fit the largest expected count. This type must be a signed integral type. 53 | * Many methods of `ref_counted` are accessed via CRTP calls to `Dervied`. This allows "overriding" them in your derived class to modify or augment their functionality. 54 | * In particular, `destroy()` member function is called to actually destroy the instance when the reference count drops to 0. The base `ref_counted` implementation invokes `delete` on `Derived` pointer. Overriding this function in derived class allows you to handle objects allocated in a different way. 55 | 56 | The template parameter `Flags` is a set of flags of type `ref_counted_flags`. These can be bitwise OR-ed to combine. 57 | By default no flags are set. Currently only two flags are defined: 58 | * `ref_counted_flags::provide_weak_references` - enables weak references 59 | * `ref_counted_flags::single_threaded` - enables single threaded mode 60 | More flags may be added in the future. 61 | 62 | The template parameter CountType can be specified to indicate the type of reference count (if `ref_counted_flags::provide_weak_references` is not set). It must be a signed integral type. 63 | 64 | For convenience the library provides the following typedefs: 65 | 66 | - `template weak_ref_counted` is a `ref_counted` with `ref_counted_flags::provide_weak_references` flag. 67 | - `template ref_counted_st` is `ref_counted` with `ref_counted_flags::single_threaed` flag. 68 | - `template weak_ref_counted_st` is a `ref_counted` with both `provide_weak_references` and `single_threaed` 69 | flags set. 70 | 71 | 72 | ### Usage 73 | 74 | You use `ref_counted` by deriving from it and making it a friend. 75 | You probably want to make your destructor private or protected (if your class is itself a base). Reference counted object should only be destroyed via reference counting, not via direct `delete` calls. 76 | 77 | 78 | Example: 79 | 80 | ```cpp 81 | class foo : public ref_counted 82 | { 83 | friend ref_counted; 84 | private: 85 | ~foo() noexcept = default; 86 | }; 87 | ``` 88 | 89 | ### Rules for constructors and destructors 90 | 91 | In your class derived from `ref_counted` you must follow these rules: 92 | 93 | - **Never increment reference count (by creating `refcnt_ptr` from `this` for example or any other means) in destructor.** Doing so will trigger an assert in debug builds and cause undefined behavior in release ones. 94 | - It is generally ok to create weak references to `this` in destructor. 95 | - Never give out `this` (by creating `refcnt_ptr` from `this` for example or any other means) or a weak reference to `this` in a constructor **if any subsequent code in constructor can exit via an exception**. If such exception is thrown your object will fail to be created leaving the outside code holding an invalid pointer. This is a general C++ rule. Usage of reference counting does not negate it. 96 | - It is ok and supported to do reference counting in constructor **if any subsequent code in constructor does not exit via exception** 97 | - Your destructor must not throw exceptions. Another general C++ rule made even more relevant here, since the reference counting machinery in `ref_counted` does not expect exception to be thrown and will break in this case. 98 | - If your class *is* a base then the destructor needs to be virtual - `ref_counted` will invoke destructor of `Derived` template argument. (If you propagate most derived class as `Derived` argument then this obviously does not apply). 99 | 100 | 101 | ### Limitations 102 | 103 | When not using weak references the implementation of `ref_counted` is standard compliant and portable (modulo bugs and compiler deficiencies of course). The only practical limitation is that total number of outstanding references to an object must be between 0 and `std::numeric_limits::max()`. 104 | 105 | When using weak references `ref_counted` relies on multiplexing pointers and reference count in the same memory location. This mechanism should work provided: 106 | * The platform uses 2s complement arithmetic 107 | * `alignof(intptr_t) > 1` 108 | * When cast to `uintptr_t` the value of a pointer to an object with alignment bigger than 1 has the lowest bit 0 (e.g. is odd). 109 | 110 | All these conditions seem to be true for all platforms these days but who knows... 111 | 112 | ``` 113 | 114 | ### Types 115 | 116 | `ref_counted` declares the following public types: 117 | 118 | * `ref_counted_base` - a synonym for itself. This allows to refer to the type from its derived types without caring about specific template arguments like `derived::ref_counted_base` 119 | * `weak_value_type` - if weak references are enabled this is the type of weak reference object this class exposes. Otherwise `void` 120 | * `weak_ptr` - if weak references are enabled a synonym for `refcnt_ptr`. Otherwise `void` 121 | * `const_weak_ptr` - if weak references are enabled a synonym for `refcnt_ptr`. Otherwise `void`. 122 | 123 | ### Constants 124 | 125 | * `static constexpr bool provides_weak_references` - `true` if this class provides weak references 126 | * `static constexpr bool single_threaded` - `true` if this class is single threaded 127 | 128 | ### Methods 129 | 130 | Unless specified otherwise all methods of this class are `noexcept`. 131 | 132 | * Copy and move constructors are **deleted**. This is a base class and slicing is meaningless. 133 | * Copy and move assignment operators are similarly **deleted** 134 | * Protected default constructor. Reference count is set to 1 in it. 135 | * Protected destructor. 136 | * Protected `void destroy() const noexcept`. Called when reference count drops to 0 to destroy the object. The default implementation calls `delete` on derived class pointer. Can be overridden in a derived class. 137 | * Public `void add_ref() const noexcept`. Increments reference count. Can be overridden in a derived class. 138 | * Public `void sub_ref() const noexcept`. Decrements reference count and destroys the object if it reaches 0. Can be overridden in a derived class. 139 | * If the class supports weak references then two additional public methods are available
140 | `weak_ptr get_weak_ptr()` and
141 | `const_weak_ptr get_weak_ptr() const`.
142 | These methods are **not** `noexcept`. Weak reference "control block" is created lazily when a weak reference is requested for the first time. If memory allocation or customized weak reference class constructor throws these methods will throw. Subsequent calls will be `noexcept`. 143 | * Protected `const weak_value_type * get_weak_value() const`. Only meaningful if the class supports weak references. This is the actual method that retrieves raw pointer to weak reference used to implement `get_weak_ptr`. Not `nonexcept`. Can be overridden in a derived class. 144 | * Protected `weak_value_type * make_weak_reference(intptr_t count) const`. Only meaningful if the class supports weak references. This method is called to create weak reference (control block) when one is needed for the first time. The returned pointer has its own reference count already incremented (as appropriate for a method returning raw pointer). Can be overridden in a derived class. 145 | 146 | 147 | ### Customizing weak reference type 148 | 149 | It is possible to customize weak reference type used by your class by "overriding" `make_weak_reference`. 150 | `ref_counted` looks via CRTP for a function with the following signature in your derived class: 151 | ```cpp 152 | some_type * make_weak_reference(intptr_t count) const 153 | ``` 154 | where some_type must be a class derived from `isptr::weak_reference` (see below). 155 | If such function exists it will be called instead of the one provided by `ref_counted` itself and the type it returns will be the weak reference type. 156 | 157 | ## Class isptr::weak_reference 158 | 159 | ```cpp 160 | template 161 | class weak_reference; 162 | ``` 163 | 164 | `weak_reference` represents a weak reference to a class derived from `ref_counted`. 165 | It usually is used as-is but can be used as a base class for customized weak references. 166 | 167 | `Owner` template parameter is the actual class the weak reference is a reference to. 168 | 169 | Internally `weak_reference` is a "control block" for a `ref_counted`. It is reference counted itself and also manages the count for the referenced object. `weak_reference` objects are created on-demand when a first weak reference is requested from `ref_counted` 170 | 171 | ### Customization 172 | 173 | You can derive your own class from `weak_reference` and use it in conjunction with your class derived from `ref_counted`. See [Customizing weak reference type](#customizing-weak-reference-type) for details. 174 | Many methods of `weak_reference` are accessed via CRTP calls to the derived class. This allows "overriding" them in your derived class to modify or augment their functionality. 175 | In particular, `destroy()` member function is called to actually destroy the instance when the reference count drops to 0. The base `weak_reference` implementation invokes `delete` on your class pointer. Overriding this function in derived class allows you to handle objects allocated in a different way. 176 | 177 | 178 | ### Usage 179 | 180 | Usually there is no need to explicitly mention `weak_reference` in your code. The type is accessible as `weak_value_type` typedef of your `ref_counted`-derived class. If you use `refcnt_ptr` you can convert between weak and strong pointers in the following fashion: 181 | 182 | ```cpp 183 | auto original = refcnt_attach(new your_object()); 184 | 185 | auto weak = weak_cast(original); 186 | //or 187 | your_object::weak_ptr weak = weak_cast(original); 188 | 189 | auto strong = strong_cast(weak); 190 | assert(strong == original || strong == nullptr); 191 | ``` 192 | 193 | You can also use member functions instead of `weak_cast`/`strong_cast` 194 | 195 | ```cpp 196 | auto original = refcnt_attach(new your_object()); 197 | 198 | auto weak = original->get_weak_ptr(); 199 | //or 200 | your_object::weak_ptr weak = original->get_weak_ptr(); 201 | 202 | auto strong = weak->lock(); 203 | assert(strong == original || strong == nullptr); 204 | ``` 205 | 206 | Note that `const`-ness propagates between strong and weak pointers. A strong pointer to const yields weak pointer to const and vice versa. 207 | 208 | 209 | ### Types 210 | 211 | `weak_reference` declares the following public types 212 | 213 | * `strong_value_type` - a synonym for `Owner` 214 | * `strong_ptr` - a synonym for `refcnt_ptr` 215 | * `const_strong_ptr` - a synonym for `refcnt_ptr` 216 | 217 | ### Methods 218 | 219 | Unless specified otherwise all methods of this class are `noexcept`. 220 | 221 | * Copy and move constructors are **deleted**. Copying of weak references is meaningless. 222 | * Copy and move assignment operators are similarly **deleted** 223 | * Protected constructor `constexpr weak_reference(intptr_t initial_strong, Owner * owner) noexcept` 224 | The `initial_strong` parameter is the initial value for the `Owner`s reference count. Since `weak_reference` objects are created on-demand the referent's count can be any value that was reached prior to `weak_reference` creation. 225 | The `owner` is a raw pointer to the `Owner` object. 226 | * Protected destructor. 227 | * Protected `void destroy() const noexcept`. Called when object's own reference count drops to 0 to destroy the object. The default implementation calls `delete` on derived class pointer. Can be overridden in a derived class. 228 | * Public `void add_ref() const noexcept`. Increments object's own reference count. Can be overridden in a derived class. 229 | * Public `void sub_ref() const noexcept`. Decrements object's own reference count and destroys the object if it reaches 0. Note that the `Owner` always holds a reference to its `weak_reference` (control block) so it will only be destroyed after the `Owner` object. Can be overridden in a derived class. 230 | * Public
231 | `const_strong_ptr lock() const noexcept` and
232 | `strong_ptr lock() noexcept`
233 | Obtain a strong reference to `Owner`. The return value is a `null` smart pointer if the owner no longer exists. 234 | * Protected
235 | `void add_owner_ref() noexcept` and
236 | `void sub_owner_ref() noexcept`
237 | These manage reference count of the `Owner`. Can be overridden in a derived class. 238 | * Protected `strong_value_type * lock_owner() const noexcept` 239 | This method does the actual locking and returns a pointer to owner (with reference count incremented) or `null`. Can be overridden in a derived class. 240 | * Protected `void on_owner_destruction() const noexcept`. 241 | This method does nothing. It can be re-declared in a derived class. It is invoked *after* the `Owner` object has been destroyed - that is when the number of strong references to it becomes 0. This provides a customization point for derived classes in case they need to clean up some data associated with the owner. Note that the method is called after owner's destruction so you cannot access or resurrect owner from it. 242 | 243 | 244 | 245 | ## Class isptr::ref_counted_adapter 246 | 247 | ```cpp 248 | template> 249 | class ref_counted_adapter; 250 | 251 | template 252 | using weak_ref_counted_adapter = ref_counted_adapter; 253 | 254 | ``` 255 | 256 | This class publicly derives from a non-reference counted class `T` and a `ref_counted`. The rest template parameters are forwarded to `ref_counted`. 257 | 258 | ### Methods 259 | 260 | * Public constructor. Perfectly forwards to constructor of `T`. `noexcept` if `T`'s constructor is. 261 | * Protected destructor. 262 | 263 | ## Class isptr::ref_counted_wrapper 264 | 265 | ```cpp 266 | template> 267 | class ref_counted_wrapper; 268 | 269 | template 270 | using weak_ref_counted_wrapper = ref_counted_wrapper; 271 | 272 | ``` 273 | 274 | This class stores (wraps) a member of class `T` and derives form `ref_counted`. The rest template parameters are forwarded to `ref_counted`. 275 | 276 | ### Members 277 | 278 | * Public `T wrapped`. The wrapped instance of `T`. 279 | 280 | ### Methods 281 | 282 | * Public constructor. Perfectly forwards to constructor of `wrapped`. `noexcept` if `T`'s constructor is. 283 | * Protected destructor. 284 | 285 | -------------------------------------------------------------------------------- /doc/refcnt_ptr.md: -------------------------------------------------------------------------------- 1 | # Header `` 2 | 3 | 4 | 5 | - [Class isptr::refcnt_ptr](#class-isptrrefcnt_ptr) 6 | - [Namespace methods](#namespace-methods) 7 | 8 | 9 | 10 | ## Class isptr::refcnt_ptr 11 | 12 | ```cpp 13 | template 14 | using refcnt_ptr = intrusive_shared_ptr; 15 | ``` 16 | 17 | `refcnt_ptr` is a specialization of [`intrusive_shared_ptr`](intrusive_shared_ptr.md) for a common case where you fully control the pointee's class and can provide traits as a nested type named `refcnt_ptr_traits`. 18 | A [`ref_counted`](ref_counted.md) base class provides such an implementation so you can easily use `ref_counted` derived classes with `refcnt_ptr`. 19 | 20 | ## Namespace methods 21 | 22 | * `template constexpr refcnt_ptr refcnt_retain(T * ptr) noexcept`. Creates `refcnt_ptr` from a raw pointer and increments the reference count. 23 | * `template constexpr refcnt_ptr refcnt_attach(T * ptr) noexcept`. Creates `refcnt_ptr` from a raw pointer without incrementing the reference count. 24 | * `template refcnt_ptr make_refcnt(Args &&... args)`. A convenience function that creates an instance of `T` via `new` and forwards the arguments to its constructor. Equivalent to `refcnt_attach(new T(args))`. 25 | * `template refcnt_ptr weak_cast(const refcnt_ptr & src)` and
26 | `template refcnt_ptr weak_cast(const refcnt_ptr & src)`.
27 | If `T` provides a type called `weak_value_type` and a method `get_weak_ptr()` it is assumed to support weak references. These functions provide a convenient "cast" conversion from a strong to a weak pointer wrapping the call to `get_weak_ptr()`. 28 | * `template refcnt_ptr strong_cast(const refcnt_ptr & src) noexcept` and
29 | `template refcnt_ptr strong_cast(const refcnt_ptr & src) noexcept`
30 | If `T` provides a type called `strong_value_type` and a method `lock()` it is assumed to be a weak reference. These functions provide a convenient "cast" conversion from a weak to a strong pointer wrapping the call to `lock()`. -------------------------------------------------------------------------------- /doc/reference_counting.md: -------------------------------------------------------------------------------- 1 | # A note on implementing reference counted objects 2 | 3 | Making an object reference counted can be tricky. There are some fundamental problems, tricky situations and some design choices that need to be taken care of. 4 | 5 | The basics are pretty well known. People usually create a class along the following lines 6 | 7 | ```cpp 8 | class foo 9 | { 10 | public: 11 | void add_ref() noexcept 12 | { ++m_count; } 13 | void sub_ref() noexcept 14 | { 15 | if (--m_count == 0) 16 | delete this; 17 | } 18 | private: 19 | atomic m_count; 20 | }; 21 | ``` 22 | 23 | So far so good but why add_ref and sub_ref are not const? A reference count is really not a part 24 | of the object state. Consider that you might want to have `smart_ptr` in your code and 25 | such smart pointer will have to call these methods on a const object. So here is the first revision 26 | 27 | ```cpp 28 | class foo 29 | { 30 | public: 31 | void add_ref() const noexcept 32 | { ++m_count; } 33 | void sub_ref() const noexcept 34 | { 35 | if (--m_count == 0) 36 | delete this; 37 | } 38 | private: 39 | mutable atomic m_count; 40 | }; 41 | ``` 42 | 43 | Now the first fundamental problem. What should the reference count be initialized to in object constructor: 44 | 1 or 0? People who use flawed smart pointers with a simple constructor `smart_ptr(T * p)` that always 45 | bumps the count tend to like 0. This way the object gets the desired one as soon as you stuff it into a smart_ptr. 46 | 47 | Unfortunately, this turns out to be a bad idea from two different angles. 48 | One is performance. Having to always increment count right after construction is a performance penalty. A small 49 | penalty but penalty nonetheless. Starting from 1 avoids it. 50 | The second problem is that while in the body of the constructor you have an object with a 0 reference count. 51 | Now, the body of the constructor is a tricky place. Consider what happens if you create a smart pointer out of `this` 52 | within the constructor. This can happen if you call a function that expects a smart pointer parameter 53 | (e.g. `register_callback(smart_ptr(this))`) and the function does not store the pointer. In this case the count 54 | is bumped to 1 when the pointer is created but then goes to 0 when it is destroyed. And this 55 | invokes the destructor from inside the constructor. Bang! You've got a nasty piece of undefined behavior happening. 56 | But wait, is creating a smart pointer from `this` in constructor a good idea? Suppose you have the following situation: 57 | 58 | ```cpp 59 | foo::foo(): 60 | base_class(...) 61 | { 62 | register_callback(smart_ptr(this)); 63 | 64 | function_that_may_throw_exception(); 65 | 66 | //other initialization 67 | } 68 | ``` 69 | 70 | and `function_that_may_throw_exception` actually throws. In this case external code possesses a pointer to an 71 | object that failed to construct. Its base classes are destroyed and it is dead from C++ point of view. Nevertheless, 72 | the external code will have a live pointer to it and, worse, invoke destructor when reference count goes to 0. 73 | Note that there is nothing special about this situation - the same bad effect can be achieved with raw pointers if we 74 | give `this` away before an exception is thrown. However, using smart pointers can provide false sense of security here. 75 | The general rule for any C++ class is: 76 | 77 | Do not give `this` away to be stored for later use if the constructor can throw after you do so. 78 | 79 | Assuming you follow this rule giving this away can be legal and legitimate - but only if you start counting from 1. 80 | To conclude, starting from 1 is faster and allows some techniques that starting from 0 doesn't. 81 | With this is mind here is a revised foo 82 | 83 | ```cpp 84 | class foo 85 | { 86 | public: 87 | void add_ref() const noexcept 88 | { ++m_count; } 89 | void sub_ref() const noexcept 90 | { 91 | if (--m_count == 0) 92 | delete this; 93 | } 94 | private: 95 | mutable atomic m_count = 1; 96 | }; 97 | ``` 98 | 99 | Note that having this convention requires you to use 100 | ```cpp 101 | intrusive_shared_ptr p = intrusive_shared_ptr::noref(new foo); 102 | ``` 103 | to **attach** newly created object rather than bump its count. 104 | 105 | But we are not off the hook yet. Now consider what happens in destructor. When we enter it, the reference 106 | count is already 0 but what if call some function there too and pass it a smart pointer created from `this`? 107 | The count will be bumped again then go to 0 and you will have `delete this` running the second time. 108 | 109 | Perhaps, the solution is again to make the count 1? If this is the case for constructor perhaps it makes sense 110 | for destructor too? This would also be a bad idea. First of all bumping the count for all objects even if they 111 | don't care, is, again, a performance penalty. 112 | Second consider what does it mean to add a reference to an object that is undergoing destruction. Conceivably the code 113 | that added the reference can then store the pointer expecting the object to be safely alive. Then it can try to 114 | use it later but the object has been already destroyed. Once you think more about it you realize that this is 115 | the famous "finalize resurrection" problem (https://en.wikipedia.org/wiki/Object_resurrection) in another form. 116 | Unfortunately, or rather fortunately, C++ doesn't allow you to "abandon" or "postpone" destruction. Once the 117 | destructor has been entered the object will become dead. 118 | 119 | It is possible to re-invent the whole notion of finalizer (a separate function called before the destructor) and 120 | resurrection for reference counting but doing so is a lot of work and doing it correctly is very tricky. The 121 | experience with finalizers in other languages is not encouraging. 122 | 123 | Instead, consider why would you ever need to give out a reference to an object from a destructor. Constructor 124 | case is obvious: you might want to register for some callback or notification from somewhere else. You might think 125 | that in a destructor it would be the opposite: deregister the object, but this is wrong. The very fact that you 126 | *are* in a destructor means that no place else in the code has any knowledge of the object. There is nowhere to 127 | deregister or disconnect from. (It is certainly possible that other places have *weak* references to the object, but 128 | those do not concern us here - there is no issue in creating a *weak* pointer to `this` in destructor and deregistering 129 | *that*.) 130 | 131 | With this in mind, the only sane approach seems to be to disallow adding a reference to an object that is being 132 | destroyed, period. Any attempt to do so likely indicates a design or implementation mistake. You could detect this 133 | situation (bumping the count from 0) and call `terminate()` or, do it in debug mode only via `assert`. 134 | 135 | 136 | ```cpp 137 | class foo 138 | { 139 | public: 140 | void add_ref() const noexcept 141 | { 142 | [[maybe_unused]] auto value = ++m_count; 143 | assert(value > 1); 144 | } 145 | void sub_ref() const noexcept 146 | { 147 | if (--m_count == 0) 148 | delete this; 149 | } 150 | private: 151 | mutable atomic m_count = 1; 152 | }; 153 | ``` 154 | 155 | Now this is the bare minimum for a reference counted class but there are a few more things to take care of. 156 | First the destructor declaration. If this class is going to be a base class for a class hierarchy then the 157 | destructor must be virtual and protected (to avoid manual deletion not done via reference counting). 158 | 159 | ```cpp 160 | class foo 161 | { 162 | //... as above 163 | protected: 164 | virtual ~foo() noexcept = default; 165 | }; 166 | ``` 167 | 168 | (If you use CRTP you can avoid `virtual` here - this is beyond the scope of this article) 169 | 170 | Alternatively if the class is standalone it should be final with a private non-virtual destructor. 171 | 172 | ```cpp 173 | class foo final 174 | { 175 | //... as above 176 | private: 177 | ~foo() noexcept = default; 178 | }; 179 | ``` 180 | 181 | The reference counting itself can also be made more efficient. Operators `++` and `--` perform a full fence whereas 182 | we don't really need it. A better approach is 183 | 184 | ```cpp 185 | class foo 186 | { 187 | public: 188 | void add_ref() const noexcept 189 | { 190 | [[maybe_unused]] auto old_value = m_count.fetch_add(1, std::memory_order_relaxed); 191 | assert(old_value > 0); 192 | } 193 | void sub_ref() const noexcept 194 | { 195 | if (m_count.fetch_sub(1, std::memory_order_release) == 1) 196 | { 197 | std::atomic_thread_fence(std::memory_order_acquire); 198 | delete this; 199 | } 200 | } 201 | protected: 202 | virtual ~foo() noexcept = default; 203 | private: 204 | mutable atomic m_count = 1; 205 | }; 206 | ``` 207 | -------------------------------------------------------------------------------- /doc/trivial_abi.md: -------------------------------------------------------------------------------- 1 | When built with CLang compiler `intrusive_shared_ptr` is marked with [\[\[clang::trivial_abi\]\]](https://clang.llvm.org/docs/AttributeReference.html#trivial-abi) attribute. A good description of what this attribute does and why it is important 2 | for performance can be found [here](https://quuxplusone.github.io/blog/2018/05/02/trivial-abi-101/). 3 | Another take on the performance issue as a comment on standard library proposal can be found 4 | [here](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1351r0.html#params). 5 | 6 | However, using trivial ABI with a type that has non trivial destructor (and of course a smart pointer destructor is non-trivial - it has to decrement reference count!) immediately raises serious objections. Such use makes the 7 | destructor run *out of order* inside the called function whereas destructors of other parameters run after the 8 | function exits. While on theoretical level this is indeed wrong, the interesting question is whether it can ever matter 9 | in practice. 10 | An important observation is that the fact that destructor runs out of order only really matters in one circumstance - 11 | when the smart pointer object holds the last reference to pointee and its destructor causes the pointee to be 12 | destroyed. This out of order destruction can be, in principle, observed by outside code. Or can it? 13 | If the smart pointer passed as a function argument holds the last reference then, for some other code to observe the 14 | pointee demise, it would need to refer to it via an unsafe raw pointer and only while the smart pointer is alive. 15 | Something like this 16 | 17 | ```cpp 18 | 19 | T * raw = ...; 20 | struct nasty 21 | { 22 | nasty(T * p): _p(p) {} 23 | ~nasty() 24 | { 25 | //use p-> here 26 | } 27 | }; 28 | foo(smart_ptr(intrusive_noref(raw)), nasty(raw)); 29 | 30 | ``` 31 | 32 | Assuming **left to right order of evaluation** for function arguments, this indeed will do bad things in `nasty` destructor. 33 | This is indeed what happens with clang on x64 MacOS. 34 | But wait a minute, even though there is a [proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0145r3.pdf) 35 | to fix the order of function arguments evaluation this is not yet part of the standard and you cannot rely on a specific 36 | order here. The code is actually broken even without trivial ABI. And indeed it is on GCC 7.4 on x64 Ubuntu. 37 | Even if the order of evaluation becomes fixed in some future C++ standard, I am sure you will agree that the code like above 38 | is, well, nasty and shouldn't exist. 39 | 40 | What about the other non-destructive cases where only reference count is modified without object destruction? In principle, 41 | this is also problematic as the value of the count can be observed. However, this is even less of an issue in practice. 42 | In all intrusive reference counted systems the specific value of reference count is meaningless, can change at any point from 43 | any thread and, in general, developers are always cautioned from even looking at it for non debugging purposes. 44 | Chances of having code somewhere that would do something wrong if the count is decremented inside, rather than outside of a 45 | function are exactly 0. 46 | 47 | So should performance of every smart pointer argument passing be penalized to handle some esoteric condition that never happens in real code? My answer is no, and this is why this library uses the trivial ABI when available. 48 | 49 | If an when the standard C++ provides a better solution for wrapper classes this decision can be revisited. 50 | 51 | 52 | -------------------------------------------------------------------------------- /inc/intrusive_shared_ptr/apple_cf_ptr.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2004 Eugene Gershnik 3 | 4 | Use of this source code is governed by a BSD-style 5 | license that can be found in the LICENSE file or at 6 | https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | */ 8 | 9 | #ifndef HEADER_APPLE_CF_PTR_H_INCLUDED 10 | #define HEADER_APPLE_CF_PTR_H_INCLUDED 11 | 12 | #if (defined(__APPLE__) && defined(__MACH__)) 13 | 14 | #include 15 | 16 | #include 17 | 18 | namespace isptr 19 | { 20 | struct cf_traits 21 | { 22 | static void add_ref(CFTypeRef ptr) noexcept 23 | { CFRetain(ptr); } 24 | static void sub_ref(CFTypeRef ptr) noexcept 25 | { CFRelease(ptr); } 26 | }; 27 | 28 | ISPTR_EXPORTED 29 | template 30 | using cf_ptr = intrusive_shared_ptr, cf_traits>; 31 | 32 | ISPTR_EXPORTED 33 | template 34 | cf_ptr cf_retain(T * ptr) { 35 | return cf_ptr::ref(ptr); 36 | } 37 | ISPTR_EXPORTED 38 | template 39 | cf_ptr cf_attach(T * ptr) { 40 | return cf_ptr::noref(ptr); 41 | } 42 | } 43 | 44 | #endif 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /inc/intrusive_shared_ptr/com_ptr.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2004 Eugene Gershnik 3 | 4 | Use of this source code is governed by a BSD-style 5 | license that can be found in the LICENSE file or at 6 | https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | */ 8 | 9 | #ifndef HEADER_COM_PTR_H_INCLUDED 10 | #define HEADER_COM_PTR_H_INCLUDED 11 | 12 | #if defined(_WIN32) 13 | 14 | #include 15 | 16 | #include 17 | 18 | namespace isptr 19 | { 20 | struct com_traits 21 | { 22 | template 23 | static std::enable_if_t, void> add_ref(T * ptr) noexcept 24 | { ptr->AddRef(); } 25 | template 26 | static std::enable_if_t, void> sub_ref(T * ptr) noexcept 27 | { ptr->Release(); } 28 | }; 29 | 30 | ISPTR_EXPORTED 31 | template 32 | using com_shared_ptr = intrusive_shared_ptr; 33 | 34 | ISPTR_EXPORTED 35 | template 36 | com_shared_ptr com_retain(T * ptr) { 37 | return com_shared_ptr::ref(ptr); 38 | } 39 | ISPTR_EXPORTED 40 | template 41 | com_shared_ptr com_attach(T * ptr) { 42 | return com_shared_ptr::noref(ptr); 43 | } 44 | } 45 | 46 | #endif 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /inc/intrusive_shared_ptr/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Eugene Gershnik 3 | 4 | Use of this source code is governed by a BSD-style 5 | license that can be found in the LICENSE file or at 6 | https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | */ 8 | 9 | #ifndef HEADER_ISPTR_COMMON_H_INCLUDED 10 | #define HEADER_ISPTR_COMMON_H_INCLUDED 11 | 12 | #if __has_include() 13 | #include 14 | #endif 15 | 16 | 17 | #if __cpp_constexpr >= 201907L 18 | 19 | #define ISPTR_CONSTEXPR_SINCE_CPP20 constexpr 20 | 21 | #else 22 | 23 | #define ISPTR_CONSTEXPR_SINCE_CPP20 24 | 25 | #endif 26 | 27 | #if __cpp_impl_three_way_comparison >= 201907L 28 | 29 | #include 30 | 31 | #define ISPTR_USE_SPACESHIP_OPERATOR 1 32 | 33 | #else 34 | 35 | #define ISPTR_USE_SPACESHIP_OPERATOR 0 36 | 37 | #endif 38 | 39 | #if __cpp_lib_out_ptr >= 202106L 40 | 41 | #define ISPTR_SUPPORT_OUT_PTR 1 42 | 43 | #else 44 | 45 | #define ISPTR_SUPPORT_OUT_PTR 0 46 | 47 | #endif 48 | 49 | //See https://github.com/llvm/llvm-project/issues/77773 for the sad story of how feature test 50 | //macros are useless with libc++ 51 | #if (__cpp_lib_format >= 201907L || (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 170000) && _LIBCPP_STD_VER >= 20) && __has_include() 52 | 53 | #include 54 | 55 | #define ISPTR_SUPPORT_STD_FORMAT 1 56 | 57 | #else 58 | 59 | #define ISPTR_SUPPORT_STD_FORMAT 0 60 | 61 | #endif 62 | 63 | 64 | #ifdef _MSC_VER 65 | 66 | #define ISPTR_ALWAYS_INLINE __forceinline 67 | #define ISPTR_TRIVIAL_ABI 68 | 69 | #elif defined(__clang__) 70 | 71 | #define ISPTR_ALWAYS_INLINE [[gnu::always_inline]] inline 72 | #define ISPTR_TRIVIAL_ABI [[clang::trivial_abi]] 73 | 74 | #elif defined (__GNUC__) 75 | 76 | #define ISPTR_ALWAYS_INLINE [[gnu::always_inline]] inline 77 | #define ISPTR_TRIVIAL_ABI 78 | 79 | #endif 80 | 81 | #ifndef ISPTR_EXPORTED 82 | #define ISPTR_EXPORTED 83 | #endif 84 | 85 | 86 | namespace isptr::internal 87 | { 88 | template 89 | constexpr bool dependent_bool = Val; 90 | } 91 | 92 | 93 | #endif 94 | 95 | -------------------------------------------------------------------------------- /inc/intrusive_shared_ptr/intrusive_shared_ptr.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2004 Eugene Gershnik 3 | 4 | Use of this source code is governed by a BSD-style 5 | license that can be found in the LICENSE file or at 6 | https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | */ 8 | 9 | #ifndef HEADER_INTRUSIVE_SHARED_PTR_H_INCLUDED 10 | #define HEADER_INTRUSIVE_SHARED_PTR_H_INCLUDED 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | namespace isptr 21 | { 22 | 23 | namespace internal 24 | { 25 | 26 | struct add_ref_detector 27 | { 28 | template 29 | auto operator()(Traits * , T * p) noexcept(noexcept(Traits::add_ref(p))) -> decltype(Traits::add_ref(p)); 30 | }; 31 | 32 | struct sub_ref_detector 33 | { 34 | template 35 | auto operator()(Traits *, T * p) noexcept(noexcept(Traits::sub_ref(p))) -> decltype(Traits::sub_ref(p)); 36 | }; 37 | 38 | } 39 | 40 | template 41 | constexpr bool are_intrusive_shared_traits = std::is_nothrow_invocable_v && 42 | std::is_nothrow_invocable_v; 43 | 44 | 45 | ISPTR_EXPORTED 46 | template 47 | class ISPTR_TRIVIAL_ABI intrusive_shared_ptr 48 | { 49 | static_assert(are_intrusive_shared_traits, "Invalid Traits for type T"); 50 | 51 | friend std::atomic>; 52 | 53 | #if ISPTR_SUPPORT_OUT_PTR 54 | friend std::out_ptr_t, T *>; 55 | friend std::inout_ptr_t, T *>; 56 | #endif 57 | 58 | public: 59 | using pointer = T *; 60 | using element_type = T; 61 | using traits_type = Traits; 62 | private: 63 | class output_param 64 | { 65 | friend class intrusive_shared_ptr; 66 | public: 67 | constexpr operator T**() && noexcept 68 | { return m_p; } 69 | 70 | private: 71 | constexpr output_param(intrusive_shared_ptr & owner) noexcept: 72 | m_p(&owner.m_p) 73 | { 74 | owner.reset(); 75 | } 76 | constexpr output_param(output_param && src) noexcept = default; 77 | 78 | output_param(const output_param &) = delete; 79 | void operator=(const output_param &) = delete; 80 | void operator=(output_param &&) = delete; 81 | private: 82 | T ** m_p; 83 | }; 84 | 85 | class inout_param 86 | { 87 | friend class intrusive_shared_ptr; 88 | public: 89 | constexpr operator T**() && noexcept 90 | { return m_p; } 91 | 92 | private: 93 | constexpr inout_param(intrusive_shared_ptr & owner) noexcept: 94 | m_p(&owner.m_p) 95 | {} 96 | constexpr inout_param(inout_param && src) noexcept = default; 97 | 98 | inout_param(const inout_param &) = delete; 99 | void operator=(const inout_param &) = delete; 100 | void operator=(inout_param &&) = delete; 101 | private: 102 | T ** m_p; 103 | }; 104 | public: 105 | static constexpr intrusive_shared_ptr noref(T * p) noexcept 106 | { return intrusive_shared_ptr(p); } 107 | static constexpr intrusive_shared_ptr ref(T * p) noexcept 108 | { 109 | intrusive_shared_ptr::do_add_ref(p); 110 | return intrusive_shared_ptr(p); 111 | } 112 | 113 | constexpr intrusive_shared_ptr() noexcept : m_p(nullptr) 114 | {} 115 | constexpr intrusive_shared_ptr(std::nullptr_t) noexcept : m_p(nullptr) 116 | {} 117 | constexpr intrusive_shared_ptr(const intrusive_shared_ptr & src) noexcept : m_p(src.m_p) 118 | { this->do_add_ref(this->m_p); } 119 | constexpr intrusive_shared_ptr(intrusive_shared_ptr && src) noexcept : m_p(src.release()) 120 | { } 121 | constexpr intrusive_shared_ptr & operator=(const intrusive_shared_ptr & src) noexcept 122 | { 123 | T * temp = this->m_p; 124 | this->m_p = src.m_p; 125 | this->do_add_ref(this->m_p); 126 | this->do_sub_ref(temp); 127 | return *this; 128 | } 129 | constexpr intrusive_shared_ptr & operator=(intrusive_shared_ptr && src) noexcept 130 | { 131 | T * new_val = src.release(); 132 | //this must come second so it is nullptr if src is us 133 | T * old_val = this->m_p; 134 | this->m_p = new_val; 135 | this->do_sub_ref(old_val); 136 | return *this; 137 | } 138 | 139 | template, void>> 140 | constexpr intrusive_shared_ptr(const intrusive_shared_ptr & src) noexcept : m_p(src.get()) 141 | { this->do_add_ref(this->m_p); } 142 | template, void>> 143 | constexpr intrusive_shared_ptr(intrusive_shared_ptr && src) noexcept : m_p(src.release()) 144 | {} 145 | template, void>> 146 | constexpr intrusive_shared_ptr(intrusive_shared_ptr && src) noexcept : m_p(src.get()) 147 | { 148 | this->do_add_ref(this->m_p); 149 | src.reset(); 150 | } 151 | template, void>> 152 | constexpr intrusive_shared_ptr & operator=(const intrusive_shared_ptr & src) noexcept 153 | { 154 | T * temp = this->m_p; 155 | this->m_p = src.get(); 156 | this->do_add_ref(this->m_p); 157 | this->do_sub_ref(temp); 158 | return *this; 159 | } 160 | template, void>> 161 | constexpr intrusive_shared_ptr & operator=(intrusive_shared_ptr && src) noexcept 162 | { 163 | this->do_sub_ref(this->m_p); 164 | this->m_p = src.release(); 165 | return *this; 166 | } 167 | template, void>> 168 | constexpr intrusive_shared_ptr & operator=(intrusive_shared_ptr && src) noexcept 169 | { 170 | this->do_sub_ref(this->m_p); 171 | this->m_p = src.get(); 172 | this->do_add_ref(this->m_p); 173 | src.reset(); 174 | return *this; 175 | } 176 | 177 | 178 | ISPTR_CONSTEXPR_SINCE_CPP20 ~intrusive_shared_ptr() noexcept 179 | { this->reset(); } 180 | 181 | constexpr T * get() const noexcept 182 | { return this->m_p; } 183 | 184 | constexpr T * operator->() const noexcept 185 | { return this->m_p; } 186 | 187 | template 188 | constexpr 189 | std::enable_if_t, 190 | X &> operator*() const noexcept 191 | { return *this->m_p; } 192 | 193 | template 194 | constexpr 195 | std::enable_if_t, 196 | M &> operator->*(M X::*memptr) const noexcept 197 | { return this->m_p->*memptr; } 198 | 199 | constexpr explicit operator bool() const noexcept 200 | { return this->m_p; } 201 | 202 | constexpr output_param get_output_param() noexcept 203 | { return output_param(*this); } 204 | 205 | constexpr inout_param get_inout_param() noexcept 206 | { return inout_param(*this); } 207 | 208 | constexpr T * release() noexcept 209 | { 210 | T * p = this->m_p; 211 | this->m_p = nullptr; 212 | return p; 213 | } 214 | 215 | ISPTR_ALWAYS_INLINE //GCC refuses to inline this otherwise 216 | constexpr void reset() noexcept 217 | { 218 | this->do_sub_ref(this->m_p); 219 | this->m_p = nullptr; 220 | } 221 | 222 | constexpr void swap(intrusive_shared_ptr & other) noexcept 223 | { 224 | T * temp = this->m_p; 225 | this->m_p = other.m_p; 226 | other.m_p = temp; 227 | } 228 | 229 | friend constexpr void swap(intrusive_shared_ptr & lhs, intrusive_shared_ptr & rhs) noexcept 230 | { lhs.swap(rhs); } 231 | 232 | template 233 | friend constexpr bool operator==(const intrusive_shared_ptr& lhs, const intrusive_shared_ptr& rhs) noexcept 234 | { return lhs.m_p == rhs.get(); } 235 | 236 | template 237 | friend constexpr bool operator==(const intrusive_shared_ptr& lhs, const Y* rhs) noexcept 238 | { return lhs.m_p == rhs; } 239 | 240 | template 241 | friend constexpr bool operator==(const Y* lhs, const intrusive_shared_ptr& rhs) noexcept 242 | { return lhs == rhs.m_p; } 243 | 244 | friend constexpr bool operator==(const intrusive_shared_ptr& lhs, std::nullptr_t) noexcept 245 | { return lhs.m_p == nullptr; } 246 | 247 | friend constexpr bool operator==(std::nullptr_t, const intrusive_shared_ptr& rhs) noexcept 248 | { return nullptr == rhs.m_p; } 249 | 250 | template 251 | friend constexpr bool operator!=(const intrusive_shared_ptr& lhs, const intrusive_shared_ptr& rhs) noexcept 252 | { return !(lhs == rhs); } 253 | 254 | template 255 | friend constexpr bool operator!=(const intrusive_shared_ptr& lhs, const Y* rhs) noexcept 256 | { return !(lhs == rhs); } 257 | 258 | template 259 | friend constexpr bool operator!=(const Y* lhs, const intrusive_shared_ptr& rhs) noexcept 260 | { return !(lhs == rhs); } 261 | 262 | friend constexpr bool operator!=(const intrusive_shared_ptr& lhs, std::nullptr_t) noexcept 263 | { return !(lhs == nullptr); } 264 | 265 | friend constexpr bool operator!=(std::nullptr_t, const intrusive_shared_ptr& rhs) noexcept 266 | { return !(nullptr == rhs); } 267 | 268 | #if ISPTR_USE_SPACESHIP_OPERATOR 269 | 270 | template 271 | friend constexpr auto operator<=>(const intrusive_shared_ptr & lhs, const intrusive_shared_ptr & rhs) noexcept 272 | { return lhs.m_p <=> rhs.get(); } 273 | 274 | template 275 | friend constexpr auto operator<=>(const intrusive_shared_ptr & lhs, const Y * rhs) noexcept 276 | { return lhs.m_p <=> rhs; } 277 | 278 | template 279 | friend constexpr auto operator<=>(const Y * lhs, const intrusive_shared_ptr & rhs) noexcept 280 | { return lhs <=> rhs.m_p; } 281 | 282 | #else 283 | template 284 | friend constexpr bool operator<(const intrusive_shared_ptr & lhs, const intrusive_shared_ptr & rhs) noexcept 285 | { return lhs.m_p < rhs.get(); } 286 | 287 | template 288 | friend constexpr bool operator<(const intrusive_shared_ptr & lhs, const Y * rhs) noexcept 289 | { return lhs.m_p < rhs; } 290 | 291 | template 292 | friend constexpr bool operator<(const Y * lhs, const intrusive_shared_ptr & rhs) noexcept 293 | { return lhs < rhs.m_p; } 294 | 295 | template 296 | friend constexpr bool operator<=(const intrusive_shared_ptr & lhs, const intrusive_shared_ptr & rhs) noexcept 297 | { return lhs.m_p <= rhs.get(); } 298 | 299 | template 300 | friend constexpr bool operator<=(const intrusive_shared_ptr & lhs, const Y * rhs) noexcept 301 | { return lhs.m_p <= rhs; } 302 | 303 | template 304 | friend constexpr bool operator<=(const Y * lhs, const intrusive_shared_ptr & rhs) noexcept 305 | { return lhs <= rhs.m_p; } 306 | 307 | template 308 | friend constexpr bool operator>(const intrusive_shared_ptr & lhs, const intrusive_shared_ptr & rhs) noexcept 309 | { return !(lhs <= rhs); } 310 | 311 | template 312 | friend constexpr bool operator>(const intrusive_shared_ptr & lhs, const Y * rhs) noexcept 313 | { return !(lhs <= rhs); } 314 | 315 | template 316 | friend constexpr bool operator>(const Y * lhs, const intrusive_shared_ptr & rhs) noexcept 317 | { return !(lhs <= rhs); } 318 | 319 | template 320 | friend constexpr bool operator>=(const intrusive_shared_ptr & lhs, const intrusive_shared_ptr & rhs) noexcept 321 | { return !(lhs < rhs); } 322 | 323 | template 324 | friend constexpr bool operator>=(const intrusive_shared_ptr & lhs, const Y * rhs) noexcept 325 | { return !(lhs < rhs); } 326 | 327 | template 328 | friend constexpr bool operator>=(const Y * lhs, const intrusive_shared_ptr & rhs) noexcept 329 | { return !(lhs < rhs); } 330 | 331 | 332 | 333 | #endif 334 | 335 | template 336 | friend std::basic_ostream & operator<<(std::basic_ostream & str, const intrusive_shared_ptr & ptr) 337 | { return str << ptr.m_p; } 338 | 339 | friend constexpr size_t hash_value(const intrusive_shared_ptr & ptr) noexcept 340 | { return std::hash()(ptr.m_p); } 341 | 342 | private: 343 | constexpr intrusive_shared_ptr(T * ptr) noexcept : 344 | m_p(ptr) 345 | {} 346 | 347 | static constexpr void do_add_ref(T * p) noexcept 348 | { if (p) Traits::add_ref(p); } 349 | static constexpr void do_sub_ref(T * p) noexcept 350 | { if (p) Traits::sub_ref(p); } 351 | private: 352 | T * m_p; 353 | }; 354 | 355 | namespace internal { 356 | 357 | template 358 | std::false_type is_intrusive_shared_ptr_helper(const T &); 359 | 360 | template 361 | std::true_type is_intrusive_shared_ptr_helper(const intrusive_shared_ptr &); 362 | } 363 | 364 | template 365 | using is_intrusive_shared_ptr = decltype(internal::is_intrusive_shared_ptr_helper(std::declval())); 366 | 367 | template 368 | bool constexpr is_intrusive_shared_ptr_v = is_intrusive_shared_ptr::value; 369 | 370 | 371 | 372 | ISPTR_EXPORTED 373 | template 374 | inline constexpr 375 | std::enable_if_t, 376 | Dest> intrusive_const_cast(intrusive_shared_ptr p) noexcept 377 | { return Dest::noref(const_cast(p.release())); } 378 | 379 | ISPTR_EXPORTED 380 | template 381 | inline constexpr 382 | std::enable_if_t, 383 | Dest> intrusive_dynamic_cast(intrusive_shared_ptr p) noexcept 384 | { 385 | auto res = dynamic_cast(p.get()); 386 | if (res) 387 | { 388 | p.release(); 389 | return Dest::noref(res); 390 | } 391 | return Dest(); 392 | } 393 | 394 | ISPTR_EXPORTED 395 | template 396 | inline constexpr 397 | std::enable_if_t, 398 | Dest> intrusive_static_cast(intrusive_shared_ptr p) noexcept 399 | { return Dest::noref(static_cast(p.release())); } 400 | } 401 | 402 | namespace std 403 | { 404 | template 405 | class atomic<::isptr::intrusive_shared_ptr> 406 | { 407 | public: 408 | using value_type = ::isptr::intrusive_shared_ptr; 409 | public: 410 | static constexpr bool is_always_lock_free = std::atomic::is_always_lock_free; 411 | 412 | constexpr atomic() noexcept = default; 413 | atomic(value_type desired) noexcept : m_p(desired.m_p) 414 | { desired.m_p = nullptr; } 415 | 416 | atomic(const atomic&) = delete; 417 | void operator=(const atomic&) = delete; 418 | 419 | ~atomic() noexcept 420 | { value_type::do_sub_ref(this->m_p.load(memory_order_acquire)); } 421 | 422 | void operator=(value_type desired) noexcept 423 | { this->store(std::move(desired)); } 424 | 425 | operator value_type() const noexcept 426 | { return this->load(); } 427 | 428 | value_type load(memory_order order = memory_order_seq_cst) const noexcept 429 | { 430 | T * ret = this->m_p.load(order); 431 | return value_type::ref(ret); 432 | } 433 | 434 | void store(value_type desired, memory_order order = memory_order_seq_cst) noexcept 435 | { exchange(std::move(desired), order); } 436 | 437 | value_type exchange(value_type desired, memory_order order = memory_order_seq_cst) noexcept 438 | { 439 | T * ret = this->m_p.exchange(desired.m_p, order); 440 | desired.m_p = nullptr; 441 | return value_type::noref(ret); 442 | } 443 | 444 | bool compare_exchange_strong(value_type & expected, value_type desired, memory_order success, memory_order failure) noexcept 445 | { 446 | T * saved_expected = expected.m_p; 447 | 448 | bool ret = this->m_p.compare_exchange_strong(expected.m_p, desired.m_p, success, failure); 449 | return post_compare_exchange(ret, saved_expected, expected, desired); 450 | } 451 | bool compare_exchange_strong(value_type & expected, value_type desired, memory_order order = memory_order_seq_cst) noexcept 452 | { 453 | T * saved_expected = expected.m_p; 454 | 455 | bool ret = this->m_p.compare_exchange_strong(expected.m_p, desired.m_p, order); 456 | return post_compare_exchange(ret, saved_expected, expected, desired); 457 | } 458 | 459 | bool compare_exchange_weak(value_type & expected, value_type desired, memory_order success, memory_order failure) noexcept 460 | { 461 | T * saved_expected = expected.m_p; 462 | 463 | bool ret = this->m_p.compare_exchange_weak(expected.m_p, desired.m_p, success, failure); 464 | return post_compare_exchange(ret, saved_expected, expected, desired); 465 | } 466 | bool compare_exchange_weak(value_type & expected, value_type desired, memory_order order = memory_order_seq_cst) noexcept 467 | { 468 | T * saved_expected = expected.m_p; 469 | 470 | bool ret = this->m_p.compare_exchange_weak(expected.m_p, desired.m_p, order); 471 | return post_compare_exchange(ret, saved_expected, expected, desired); 472 | } 473 | 474 | 475 | bool is_lock_free() const noexcept 476 | { return this->m_p.is_lock_free(); } 477 | 478 | private: 479 | static bool post_compare_exchange(bool exchange_result, T * saved_expected, 480 | value_type & expected, value_type & desired) noexcept 481 | { 482 | if (exchange_result) 483 | { 484 | //success: we are desired and expected is unchanged 485 | desired.m_p = nullptr; 486 | //saved_expected is equal to our original value which we need to sub_ref 487 | value_type::do_sub_ref(saved_expected); 488 | } 489 | else 490 | { 491 | //failure: expected is us and desired is unchanged. 492 | value_type::do_add_ref(expected.m_p); //our value going out 493 | value_type::do_sub_ref(saved_expected); //old expected 494 | } 495 | return exchange_result; 496 | } 497 | private: 498 | std::atomic m_p = nullptr; 499 | }; 500 | 501 | #if ISPTR_SUPPORT_OUT_PTR 502 | 503 | template 504 | class out_ptr_t<::isptr::intrusive_shared_ptr, T *> 505 | { 506 | public: 507 | constexpr out_ptr_t(::isptr::intrusive_shared_ptr & owner) noexcept: 508 | m_p(&owner.m_p) 509 | { 510 | owner.reset(); 511 | } 512 | constexpr out_ptr_t(out_ptr_t && src) noexcept = default; 513 | out_ptr_t(const out_ptr_t &) = delete; 514 | 515 | void operator=(const out_ptr_t &) = delete; 516 | void operator=(out_ptr_t &&) = delete; 517 | 518 | constexpr operator T**() const noexcept 519 | { return m_p; } 520 | 521 | constexpr operator void**() const noexcept requires(!std::is_same_v) 522 | { return reinterpret_cast(m_p); } 523 | private: 524 | T ** m_p; 525 | }; 526 | 527 | template 528 | class inout_ptr_t<::isptr::intrusive_shared_ptr, T *> 529 | { 530 | public: 531 | constexpr inout_ptr_t(::isptr::intrusive_shared_ptr & owner) noexcept : 532 | m_p(&owner.m_p) 533 | {} 534 | constexpr inout_ptr_t(inout_ptr_t && src) noexcept = default; 535 | inout_ptr_t(const inout_ptr_t &) = delete; 536 | 537 | void operator=(const inout_ptr_t &) = delete; 538 | void operator=(inout_ptr_t &&) = delete; 539 | 540 | constexpr operator T**() const noexcept 541 | { return m_p; } 542 | 543 | constexpr operator void**() const noexcept requires(!std::is_same_v) 544 | { return reinterpret_cast(m_p); } 545 | private: 546 | T ** m_p; 547 | }; 548 | 549 | #endif 550 | 551 | #if ISPTR_SUPPORT_STD_FORMAT 552 | 553 | template 554 | struct formatter<::isptr::intrusive_shared_ptr, CharT> : public formatter 555 | { 556 | template 557 | auto format(const ::isptr::intrusive_shared_ptr & ptr, FormatContext & ctx) const -> decltype(ctx.out()) 558 | { return formatter::format(ptr.get(), ctx); } 559 | }; 560 | #endif 561 | 562 | template 563 | struct hash<::isptr::intrusive_shared_ptr> 564 | { 565 | constexpr size_t operator()(const ::isptr::intrusive_shared_ptr & ptr) const noexcept 566 | { return hash_value(ptr); } 567 | }; 568 | } 569 | 570 | #undef ISPTR_TRIVIAL_ABI 571 | #undef ISPTR_CONSTEXPR_SINCE_CPP20 572 | #undef ISPTR_USE_SPACESHIP_OPERATOR 573 | #undef ISPTR_SUPPORT_OUT_PTR 574 | 575 | #endif 576 | 577 | -------------------------------------------------------------------------------- /inc/intrusive_shared_ptr/python_ptr.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Eugene Gershnik 3 | 4 | Use of this source code is governed by a BSD-style 5 | license that can be found in the LICENSE file or at 6 | https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | */ 8 | 9 | #ifndef HEADER_PYTHON_PTR_H_INCLUDED 10 | #define HEADER_PYTHON_PTR_H_INCLUDED 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace isptr 17 | { 18 | struct py_traits { 19 | static void add_ref(PyObject * ptr) noexcept 20 | { Py_INCREF(ptr); } 21 | static void sub_ref(PyObject * ptr) noexcept 22 | { Py_DECREF(ptr); } 23 | 24 | static void add_ref(PyTypeObject * ptr) noexcept 25 | { Py_INCREF(ptr); } 26 | static void sub_ref(PyTypeObject * ptr) noexcept 27 | { Py_DECREF(ptr); } 28 | }; 29 | 30 | template 31 | using py_ptr = intrusive_shared_ptr; 32 | 33 | ISPTR_EXPORTED 34 | template 35 | py_ptr py_retain(T * ptr) { 36 | return py_ptr::ref(ptr); 37 | } 38 | ISPTR_EXPORTED 39 | template 40 | py_ptr py_attach(T * ptr) { 41 | return py_ptr::noref(ptr); 42 | } 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /inc/intrusive_shared_ptr/refcnt_ptr.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2004 Eugene Gershnik 3 | 4 | Use of this source code is governed by a BSD-style 5 | license that can be found in the LICENSE file or at 6 | https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | */ 8 | 9 | #ifndef HEADER_REFCNT_PTR_H_INCLUDED 10 | #define HEADER_REFCNT_PTR_H_INCLUDED 11 | 12 | #include 13 | 14 | namespace isptr 15 | { 16 | 17 | ISPTR_EXPORTED 18 | template 19 | using refcnt_ptr = intrusive_shared_ptr; 20 | 21 | ISPTR_EXPORTED 22 | template 23 | constexpr refcnt_ptr refcnt_retain(T * ptr) noexcept { 24 | return refcnt_ptr::ref(ptr); 25 | } 26 | 27 | ISPTR_EXPORTED 28 | template 29 | constexpr refcnt_ptr refcnt_attach(T * ptr) noexcept { 30 | return refcnt_ptr::noref(ptr); 31 | } 32 | 33 | ISPTR_EXPORTED 34 | template 35 | inline refcnt_ptr make_refcnt(Args &&... args) { 36 | return refcnt_ptr::noref(new T( std::forward(args)... )); 37 | } 38 | 39 | ISPTR_EXPORTED 40 | template 41 | inline 42 | refcnt_ptr weak_cast(const refcnt_ptr & src) { 43 | return src->get_weak_ptr(); 44 | } 45 | 46 | ISPTR_EXPORTED 47 | template 48 | inline 49 | refcnt_ptr weak_cast(const refcnt_ptr & src) { 50 | return src->get_weak_ptr(); 51 | } 52 | 53 | ISPTR_EXPORTED 54 | template 55 | inline 56 | refcnt_ptr strong_cast(const refcnt_ptr & src) noexcept { 57 | return src->lock(); 58 | } 59 | 60 | ISPTR_EXPORTED 61 | template 62 | inline 63 | refcnt_ptr strong_cast(const refcnt_ptr & src) noexcept { 64 | return src->lock(); 65 | } 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #cmake_policy(SET CMP0092 NEW) 2 | 3 | if (WIN32) 4 | set(CMAKE_SYSTEM_VERSION "10.0.19041.0" CACHE STRING "Windows SDK Version" FORCE) 5 | endif() 6 | 7 | 8 | include(FetchContent) 9 | 10 | FetchContent_Declare(doctest 11 | URL https://raw.githubusercontent.com/doctest/doctest/v2.4.11/doctest/doctest.h 12 | DOWNLOAD_NO_EXTRACT TRUE 13 | SOURCE_DIR downloaded/doctest 14 | ) 15 | 16 | FetchContent_MakeAvailable(doctest) 17 | 18 | if(ISPTR_ENABLE_PYTHON AND NOT Python3_Development_FOUND) 19 | find_package (Python3 COMPONENTS Development REQUIRED) 20 | endif() 21 | 22 | 23 | set (TEST_SUFFIXES 24 | 17 25 | 20 26 | 23 27 | ) 28 | 29 | set(CXX_STANDARD_17 17) 30 | set(CXX_STANDARD_20 20) 31 | set(CXX_STANDARD_23 23) 32 | 33 | set(TEST_VARIANTS_17 "headers") 34 | set(TEST_VARIANTS_20 "headers") 35 | set(TEST_VARIANTS_23 "headers") 36 | 37 | if (${ISPTR_ENABLE_MODULE}) 38 | if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.28") 39 | list(APPEND TEST_VARIANTS_20 "module") 40 | list(APPEND TEST_VARIANTS_23 "module") 41 | else() 42 | message(WARNING "CMake version ${CMAKE_VERSION} doesn't support modules, ignoring ISPTR_ENABLE_MODULE") 43 | endif() 44 | endif() 45 | 46 | set(TEST_COMMAND "") 47 | 48 | foreach(TEST_SUFFIX ${TEST_SUFFIXES}) 49 | 50 | foreach(TEST_VARIANT ${TEST_VARIANTS_${TEST_SUFFIX}}) 51 | 52 | set(TEST_TARGET_NAME test-${TEST_SUFFIX}-${TEST_VARIANT}) 53 | 54 | add_executable(${TEST_TARGET_NAME} EXCLUDE_FROM_ALL) 55 | 56 | set_target_properties(${TEST_TARGET_NAME} PROPERTIES 57 | CXX_STANDARD ${CXX_STANDARD_${TEST_SUFFIX}} 58 | CXX_STANDARD_REQUIRED OFF 59 | CXX_EXTENSIONS OFF 60 | CXX_VISIBILITY_PRESET hidden 61 | VISIBILITY_INLINES_HIDDEN ON 62 | ) 63 | 64 | if("${TEST_VARIANT}" STREQUAL "module") 65 | set_target_properties(${TEST_TARGET_NAME} PROPERTIES 66 | CXX_SCAN_FOR_MODULES ON 67 | ) 68 | endif() 69 | 70 | target_include_directories(${TEST_TARGET_NAME} PRIVATE 71 | ${CMAKE_CURRENT_BINARY_DIR}/downloaded 72 | ) 73 | 74 | if (ISPTR_ENABLE_PYTHON) 75 | target_link_libraries(${TEST_TARGET_NAME} PRIVATE 76 | ${Python3_LIBRARIES} 77 | ) 78 | target_include_directories(${TEST_TARGET_NAME} SYSTEM PRIVATE 79 | ${Python3_INCLUDE_DIRS} 80 | ) 81 | endif() 82 | 83 | target_link_libraries(${TEST_TARGET_NAME} PRIVATE 84 | "$<$:-framework CoreFoundation>" 85 | "$<$>:isptr::isptr>" 86 | ) 87 | 88 | target_compile_options(${TEST_TARGET_NAME} PRIVATE 89 | $<$:/W4;/WX> 90 | $<$:-Wall;-Wextra;-pedantic;-Wno-self-assign-overloaded;-Wno-self-move> 91 | $<$:-Wall;-Wextra;-pedantic;-Wno-self-assign-overloaded;-Wno-self-move> 92 | $<$:-Wall;-Wextra;-pedantic;-Wno-self-move> 93 | ) 94 | 95 | target_compile_definitions(${TEST_TARGET_NAME} PRIVATE 96 | $<$:ISPTR_USE_PYTHON=1> 97 | $<$:ISPTR_USE_MODULES=1> 98 | _FILE_OFFSET_BITS=64 # prevents weird issues with modules and clang on Ubuntu 99 | ) 100 | 101 | if (${TEST_VARIANT} STREQUAL "module") 102 | isptr_add_module(${TEST_TARGET_NAME} PRIVATE) 103 | endif() 104 | 105 | target_sources(${TEST_TARGET_NAME} PRIVATE 106 | 107 | test_apple_cf_ptr.cpp 108 | test_atomic.cpp 109 | test_com_ptr.cpp 110 | test_python_ptr.cpp 111 | test_general.cpp 112 | test_main.cpp 113 | test_out_ptr.cpp 114 | test_ref_counted.cpp 115 | test_ref_counted_st.cpp 116 | test_weak_ref_counted.cpp 117 | test_weak_ref_counted_st.cpp 118 | test_abstract_ref_counted.cpp 119 | test_abstract_ref_counted_st.cpp 120 | test_delegating_traits.cpp 121 | 122 | mocks.h 123 | ) 124 | 125 | list(APPEND TEST_COMMAND COMMAND) 126 | list(APPEND TEST_COMMAND echo Running: ${TEST_TARGET_NAME}) 127 | list(APPEND TEST_COMMAND COMMAND) 128 | list(APPEND TEST_COMMAND ${TEST_TARGET_NAME} -ni -fc) 129 | 130 | endforeach() 131 | 132 | endforeach() 133 | 134 | add_custom_target(run-test 135 | ${TEST_COMMAND} 136 | ) 137 | -------------------------------------------------------------------------------- /test/mocks.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_HEADER_MOCKS_H_INCLUDED 2 | #define TEST_HEADER_MOCKS_H_INCLUDED 3 | 4 | template 5 | struct instrumented_counted 6 | { 7 | mutable int count = 1; 8 | 9 | virtual ~instrumented_counted() 10 | { 11 | CHECK(this->count == -1); 12 | } 13 | }; 14 | 15 | template 16 | struct derived_instrumented_counted : instrumented_counted 17 | {}; 18 | 19 | struct non_counted 20 | {}; 21 | 22 | 23 | template 24 | struct mock_traits 25 | { 26 | template 27 | static void add_ref(const instrumented_counted * c) noexcept 28 | { 29 | REQUIRE(c->count > 0); 30 | ++c->count; 31 | } 32 | 33 | template 34 | static void sub_ref(const instrumented_counted * c) noexcept 35 | { 36 | CHECK(c->count > 0); 37 | if (--c->count == 0) 38 | c->count = -1; 39 | } 40 | }; 41 | 42 | template 43 | using mock_ptr = isptr::intrusive_shared_ptr>; 44 | 45 | template 46 | using mock_ptr_different_traits = isptr::intrusive_shared_ptr>; 47 | 48 | template 49 | mock_ptr mock_ref(T * ptr) { 50 | return mock_ptr::ref(ptr); 51 | } 52 | template 53 | mock_ptr mock_noref(T * ptr) { 54 | return mock_ptr::noref(ptr); 55 | } 56 | 57 | template 58 | mock_ptr_different_traits mock_ref_different_traits(T * ptr) { 59 | return mock_ptr_different_traits::ref(ptr); 60 | } 61 | template 62 | mock_ptr_different_traits mock_noref_different_traits(T * ptr) { 63 | return mock_ptr_different_traits::noref(ptr); 64 | } 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /test/test_abstract_ref_counted.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #include 4 | #endif 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if ISPTR_USE_MODULES 11 | import isptr; 12 | #endif 13 | 14 | using namespace isptr; 15 | 16 | 17 | namespace 18 | { 19 | bool counted_add_ref_called = false; 20 | bool counted_sub_ref_called = false; 21 | bool counted_destroy_called = false; 22 | bool make_weak_reference_called = false; 23 | bool get_weak_value_called = false; 24 | 25 | class abstract_weak_reference; 26 | 27 | class abstract_ref_counted : public ref_counted 28 | { 29 | friend ref_counted; 30 | public: 31 | virtual void add_ref() const noexcept final 32 | { 33 | counted_add_ref_called = true; 34 | ref_counted::add_ref(); 35 | } 36 | 37 | virtual void sub_ref() const noexcept final 38 | { 39 | counted_sub_ref_called = true; 40 | ref_counted::sub_ref(); 41 | } 42 | protected: 43 | virtual ~abstract_ref_counted() noexcept 44 | {} 45 | 46 | virtual void destroy() const noexcept 47 | { 48 | counted_destroy_called = true; 49 | ref_counted::destroy(); 50 | } 51 | 52 | virtual abstract_weak_reference * make_weak_reference(intptr_t count) const; 53 | 54 | virtual const weak_reference * get_weak_value() const 55 | { 56 | get_weak_value_called = true; 57 | return ref_counted::get_weak_value(); 58 | } 59 | }; 60 | 61 | bool weak_add_ref_called = false; 62 | bool weak_sub_ref_called = false; 63 | bool weak_destroy_called = false; 64 | bool add_owner_ref_called = false; 65 | bool sub_owner_ref_called = false; 66 | bool lock_owner_called = false; 67 | bool on_owner_destruction_called = false; 68 | 69 | class abstract_weak_reference : public weak_reference 70 | { 71 | friend weak_reference; 72 | friend abstract_ref_counted; 73 | 74 | public: 75 | virtual void add_ref() const noexcept final 76 | { 77 | weak_add_ref_called = true; 78 | weak_reference::add_ref(); 79 | } 80 | 81 | virtual void sub_ref() const noexcept final 82 | { 83 | weak_sub_ref_called = true; 84 | weak_reference::sub_ref(); 85 | } 86 | 87 | protected: 88 | virtual ~abstract_weak_reference() noexcept 89 | {} 90 | 91 | virtual void destroy() const noexcept 92 | { 93 | weak_destroy_called = true; 94 | weak_reference::destroy(); 95 | } 96 | 97 | private: 98 | abstract_weak_reference(intptr_t count, abstract_ref_counted * owner): 99 | weak_reference(count, owner) 100 | {} 101 | 102 | virtual void add_owner_ref() noexcept 103 | { 104 | add_owner_ref_called = true; 105 | weak_reference::add_owner_ref(); 106 | } 107 | 108 | virtual void sub_owner_ref() noexcept 109 | { 110 | sub_owner_ref_called = true; 111 | weak_reference::sub_owner_ref(); 112 | } 113 | 114 | virtual abstract_ref_counted * lock_owner() const noexcept 115 | { 116 | lock_owner_called = true; 117 | return static_cast(weak_reference::lock_owner()); 118 | } 119 | 120 | virtual void on_owner_destruction() const noexcept 121 | { 122 | on_owner_destruction_called = true; 123 | } 124 | }; 125 | 126 | inline auto abstract_ref_counted::make_weak_reference(intptr_t count) const -> abstract_weak_reference * 127 | { 128 | make_weak_reference_called = true; 129 | return new abstract_weak_reference(count, const_cast(this)); 130 | } 131 | 132 | class simple : public abstract_ref_counted 133 | { 134 | 135 | }; 136 | } 137 | 138 | TEST_SUITE("abstract_ref_counted") { 139 | 140 | TEST_CASE( "Abstract ref counted works" ) { 141 | 142 | SUBCASE( "Simple" ) { 143 | auto p = refcnt_attach(new simple()); 144 | decltype(p) p1 = p; 145 | p1.reset(); 146 | auto w = weak_cast(p); 147 | static_assert(std::is_same_v>, "invalid weak reference type"); 148 | auto w1 = p->get_weak_ptr(); 149 | static_assert(std::is_same_v>, "invalid weak reference type"); 150 | auto p2 = strong_cast(w); 151 | auto p3 = p2; 152 | static_assert(std::is_same_v, "invalid weak reference type"); 153 | 154 | p.reset(); 155 | p2.reset(); 156 | p3.reset(); 157 | w.reset(); 158 | w1.reset(); 159 | 160 | CHECK(counted_add_ref_called); 161 | CHECK(counted_sub_ref_called); 162 | CHECK(counted_destroy_called); 163 | CHECK(make_weak_reference_called); 164 | CHECK(get_weak_value_called); 165 | CHECK(weak_add_ref_called); 166 | CHECK(weak_sub_ref_called); 167 | CHECK(weak_destroy_called); 168 | CHECK(add_owner_ref_called); 169 | CHECK(sub_owner_ref_called); 170 | CHECK(lock_owner_called); 171 | CHECK(on_owner_destruction_called); 172 | } 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /test/test_abstract_ref_counted_st.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #include 4 | #endif 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if ISPTR_USE_MODULES 11 | import isptr; 12 | #endif 13 | 14 | using namespace isptr; 15 | 16 | 17 | namespace 18 | { 19 | bool counted_add_ref_called = false; 20 | bool counted_sub_ref_called = false; 21 | bool counted_destroy_called = false; 22 | bool make_weak_reference_called = false; 23 | bool get_weak_value_called = false; 24 | 25 | class abstract_weak_reference; 26 | 27 | class abstract_ref_counted : public ref_counted 30 | { 31 | friend ref_counted; 32 | public: 33 | virtual void add_ref() const noexcept final 34 | { 35 | counted_add_ref_called = true; 36 | ref_counted::add_ref(); 37 | } 38 | 39 | virtual void sub_ref() const noexcept final 40 | { 41 | counted_sub_ref_called = true; 42 | ref_counted::sub_ref(); 43 | } 44 | protected: 45 | virtual ~abstract_ref_counted() noexcept 46 | {} 47 | 48 | virtual void destroy() const noexcept 49 | { 50 | counted_destroy_called = true; 51 | ref_counted::destroy(); 52 | } 53 | 54 | virtual abstract_weak_reference * make_weak_reference(intptr_t count) const; 55 | 56 | virtual const weak_reference * get_weak_value() const 57 | { 58 | get_weak_value_called = true; 59 | return ref_counted::get_weak_value(); 60 | } 61 | }; 62 | 63 | bool weak_add_ref_called = false; 64 | bool weak_sub_ref_called = false; 65 | bool weak_destroy_called = false; 66 | bool add_owner_ref_called = false; 67 | bool sub_owner_ref_called = false; 68 | bool lock_owner_called = false; 69 | bool on_owner_destruction_called = false; 70 | 71 | class abstract_weak_reference : public weak_reference 72 | { 73 | friend weak_reference; 74 | friend abstract_ref_counted; 75 | 76 | public: 77 | virtual void add_ref() const noexcept final 78 | { 79 | weak_add_ref_called = true; 80 | weak_reference::add_ref(); 81 | } 82 | 83 | virtual void sub_ref() const noexcept final 84 | { 85 | weak_sub_ref_called = true; 86 | weak_reference::sub_ref(); 87 | } 88 | 89 | protected: 90 | virtual ~abstract_weak_reference() noexcept 91 | {} 92 | 93 | virtual void destroy() const noexcept 94 | { 95 | weak_destroy_called = true; 96 | weak_reference::destroy(); 97 | } 98 | 99 | private: 100 | abstract_weak_reference(intptr_t count, abstract_ref_counted * owner): 101 | weak_reference(count, owner) 102 | {} 103 | 104 | virtual void add_owner_ref() noexcept 105 | { 106 | add_owner_ref_called = true; 107 | weak_reference::add_owner_ref(); 108 | } 109 | 110 | virtual void sub_owner_ref() noexcept 111 | { 112 | sub_owner_ref_called = true; 113 | weak_reference::sub_owner_ref(); 114 | } 115 | 116 | virtual abstract_ref_counted * lock_owner() const noexcept 117 | { 118 | lock_owner_called = true; 119 | return static_cast(weak_reference::lock_owner()); 120 | } 121 | 122 | virtual void on_owner_destruction() const noexcept 123 | { 124 | on_owner_destruction_called = true; 125 | } 126 | }; 127 | 128 | inline auto abstract_ref_counted::make_weak_reference(intptr_t count) const -> abstract_weak_reference * 129 | { 130 | make_weak_reference_called = true; 131 | return new abstract_weak_reference(count, const_cast(this)); 132 | } 133 | 134 | class simple : public abstract_ref_counted 135 | { 136 | 137 | }; 138 | } 139 | 140 | TEST_SUITE("abstract_ref_counted_st") { 141 | 142 | 143 | TEST_CASE( "Abstract ref counted st works" ) { 144 | 145 | SUBCASE( "Simple" ) { 146 | auto p = refcnt_attach(new simple()); 147 | decltype(p) p1 = p; 148 | p1.reset(); 149 | auto w = weak_cast(p); 150 | static_assert(std::is_same_v>, "invalid weak reference type"); 151 | auto w1 = p->get_weak_ptr(); 152 | static_assert(std::is_same_v>, "invalid weak reference type"); 153 | auto p2 = strong_cast(w); 154 | auto p3 = p2; 155 | static_assert(std::is_same_v, "invalid weak reference type"); 156 | 157 | p.reset(); 158 | p2.reset(); 159 | p3.reset(); 160 | w.reset(); 161 | w1.reset(); 162 | 163 | CHECK(counted_add_ref_called); 164 | CHECK(counted_sub_ref_called); 165 | CHECK(counted_destroy_called); 166 | CHECK(make_weak_reference_called); 167 | CHECK(get_weak_value_called); 168 | CHECK(weak_add_ref_called); 169 | CHECK(weak_sub_ref_called); 170 | CHECK(weak_destroy_called); 171 | CHECK(add_owner_ref_called); 172 | CHECK(sub_owner_ref_called); 173 | CHECK(lock_owner_called); 174 | CHECK(on_owner_destruction_called); 175 | } 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /test/test_apple_cf_ptr.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #endif 4 | 5 | #include 6 | 7 | #if ISPTR_USE_MODULES 8 | import isptr; 9 | #endif 10 | 11 | #if (defined(__APPLE__) && defined(__MACH__)) 12 | 13 | using namespace isptr; 14 | 15 | TEST_SUITE("apple") { 16 | 17 | TEST_CASE( "Apple Ptr" ) { 18 | 19 | auto str = cf_attach(CFStringCreateWithCString(nullptr, "Hello", kCFStringEncodingUTF8)); 20 | CHECK( CFStringGetLength(str.get()) == 5 ); 21 | } 22 | 23 | } 24 | 25 | #endif 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/test_atomic.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #endif 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #if ISPTR_USE_MODULES 11 | import isptr; 12 | #endif 13 | 14 | #include "mocks.h" 15 | 16 | using namespace isptr; 17 | 18 | TEST_SUITE("traits") { 19 | 20 | TEST_CASE( "Atomic type traits are correct" ) { 21 | 22 | using ptr = std::atomic>>; 23 | 24 | SUBCASE("Construction, destruction and assignment") { 25 | 26 | CHECK( sizeof(ptr) == sizeof(std::atomic *>) ); 27 | CHECK( std::alignment_of_v == std::alignment_of_v *>> ); 28 | 29 | CHECK( std::is_default_constructible_v ); 30 | CHECK( std::is_nothrow_default_constructible_v ); 31 | CHECK( !std::is_trivially_default_constructible_v ); 32 | 33 | CHECK( !std::is_copy_constructible_v ); 34 | CHECK( !std::is_move_constructible_v ); 35 | 36 | CHECK( !std::is_copy_assignable_v ); 37 | CHECK( !std::is_move_assignable_v ); 38 | 39 | CHECK( !std::is_swappable_v ); 40 | 41 | CHECK( std::is_destructible_v ); 42 | CHECK( !std::is_trivially_destructible_v ); 43 | CHECK( std::is_nothrow_destructible_v ); 44 | 45 | CHECK( !std::is_assignable_v ); 46 | 47 | CHECK( std::is_constructible_v>> ); 48 | CHECK( std::is_nothrow_constructible_v>> ); 49 | CHECK( !std::is_trivially_constructible_v>> ); 50 | 51 | CHECK( std::is_assignable_v>> ); 52 | CHECK( std::is_nothrow_assignable_v>> ); 53 | CHECK( !std::is_trivially_assignable_v>> ); 54 | 55 | CHECK( std::is_convertible_v>> ); 56 | //CHECK( std::is_nothrow_convertible_v>> ); 57 | } 58 | } 59 | 60 | TEST_CASE( "Atomic load" ) { 61 | 62 | SUBCASE( "Explicit" ) { 63 | instrumented_counted<> object; 64 | std::atomic>> ptr = mock_noref(&object); 65 | auto ptr1 = ptr.load(); 66 | CHECK( ptr1.get() == &object ); 67 | CHECK( object.count == 2 ); 68 | } 69 | 70 | SUBCASE( "Implicit" ) { 71 | instrumented_counted<> object; 72 | std::atomic>> ptr = mock_noref(&object); 73 | mock_ptr> ptr1 = ptr; 74 | CHECK( ptr1.get() == &object ); 75 | CHECK( object.count == 2 ); 76 | } 77 | 78 | SUBCASE( "Free function" ) { 79 | instrumented_counted<> object; 80 | std::atomic>> ptr = mock_noref(&object); 81 | mock_ptr> ptr1 = std::atomic_load(&ptr); 82 | CHECK( ptr1.get() == &object ); 83 | CHECK( object.count == 2 ); 84 | } 85 | 86 | } 87 | 88 | TEST_CASE( "Atomic store" ) { 89 | 90 | SUBCASE( "Explicit" ) { 91 | instrumented_counted<> object1, object2; 92 | std::atomic>> ptr = mock_noref(&object1); 93 | auto ptr1 = mock_noref(&object2); 94 | 95 | ptr.store(ptr1); 96 | CHECK( ptr1.get() == &object2 ); 97 | CHECK( ptr.load().get() == &object2 ); 98 | CHECK( object1.count == -1 ); 99 | CHECK( object2.count == 2 ); 100 | } 101 | 102 | SUBCASE( "Implicit" ) { 103 | instrumented_counted<> object1, object2; 104 | std::atomic>> ptr = mock_noref(&object1); 105 | auto ptr1 = mock_noref(&object2); 106 | 107 | ptr = ptr1; 108 | CHECK( ptr1.get() == &object2 ); 109 | CHECK( ptr.load().get() == &object2 ); 110 | CHECK( object1.count == -1 ); 111 | CHECK( object2.count == 2 ); 112 | } 113 | 114 | SUBCASE( "Free function" ) { 115 | instrumented_counted<> object1, object2; 116 | std::atomic>> ptr = mock_noref(&object1); 117 | auto ptr1 = mock_noref(&object2); 118 | 119 | std::atomic_store(&ptr, ptr1); 120 | CHECK( ptr1.get() == &object2 ); 121 | CHECK( ptr.load().get() == &object2 ); 122 | CHECK( object1.count == -1 ); 123 | CHECK( object2.count == 2 ); 124 | } 125 | 126 | } 127 | 128 | TEST_CASE( "Atomic comapre and exchange" ) { 129 | 130 | SUBCASE( "Strong" ) { 131 | instrumented_counted<> object1, object2, object3; 132 | std::atomic>> ptr1 = mock_noref(&object1); 133 | auto ptr2 = mock_noref(&object2); 134 | auto ptr3 = mock_noref(&object3); 135 | 136 | auto res = ptr1.compare_exchange_strong(ptr2, ptr3); 137 | CHECK( !res ); 138 | CHECK( ptr1.load().get() == &object1 ); 139 | CHECK( ptr2.get() == &object1 ); 140 | CHECK( ptr3.get() == &object3 ); 141 | CHECK( object1.count == 2 ); 142 | CHECK( object2.count == -1 ); 143 | CHECK( object3.count == 1 ); 144 | 145 | res = ptr1.compare_exchange_strong(ptr2, ptr3); 146 | CHECK( res ); 147 | CHECK( ptr1.load().get() == &object3 ); 148 | CHECK( ptr2.get() == &object1 ); 149 | CHECK( ptr3.get() == &object3 ); 150 | CHECK( object1.count == 1 ); 151 | CHECK( object3.count == 2 ); 152 | } 153 | 154 | SUBCASE( "Strong 2 arg" ) { 155 | instrumented_counted<> object1, object2, object3; 156 | std::atomic>> ptr1 = mock_noref(&object1); 157 | auto ptr2 = mock_noref(&object2); 158 | auto ptr3 = mock_noref(&object3); 159 | 160 | auto res = ptr1.compare_exchange_strong(ptr2, ptr3, std::memory_order_seq_cst, std::memory_order_seq_cst); 161 | CHECK( !res ); 162 | CHECK( ptr1.load().get() == &object1 ); 163 | CHECK( ptr2.get() == &object1 ); 164 | CHECK( ptr3.get() == &object3 ); 165 | CHECK( object1.count == 2 ); 166 | CHECK( object2.count == -1 ); 167 | CHECK( object3.count == 1 ); 168 | 169 | res = ptr1.compare_exchange_strong(ptr2, ptr3, std::memory_order_seq_cst, std::memory_order_seq_cst); 170 | CHECK( res ); 171 | CHECK( ptr1.load().get() == &object3 ); 172 | CHECK( ptr2.get() == &object1 ); 173 | CHECK( ptr3.get() == &object3 ); 174 | CHECK( object1.count == 1 ); 175 | CHECK( object3.count == 2 ); 176 | } 177 | 178 | SUBCASE( "Weak" ) { 179 | instrumented_counted<> object1, object2, object3; 180 | std::atomic>> ptr1 = mock_noref(&object1); 181 | auto ptr2 = mock_noref(&object2); 182 | auto ptr3 = mock_noref(&object3); 183 | 184 | auto res = ptr1.compare_exchange_weak(ptr2, ptr3); 185 | CHECK( !res ); 186 | CHECK( ptr1.load().get() == &object1 ); 187 | CHECK( ptr2.get() == &object1 ); 188 | CHECK( ptr3.get() == &object3 ); 189 | CHECK( object1.count == 2 ); 190 | CHECK( object2.count == -1 ); 191 | CHECK( object3.count == 1 ); 192 | 193 | res = ptr1.compare_exchange_weak(ptr2, ptr3); 194 | CHECK( res ); 195 | CHECK( ptr1.load().get() == &object3 ); 196 | CHECK( ptr2.get() == &object1 ); 197 | CHECK( ptr3.get() == &object3 ); 198 | CHECK( object1.count == 1 ); 199 | CHECK( object3.count == 2 ); 200 | } 201 | 202 | SUBCASE( "Weak 2 arg" ) { 203 | instrumented_counted<> object1, object2, object3; 204 | std::atomic>> ptr1 = mock_noref(&object1); 205 | auto ptr2 = mock_noref(&object2); 206 | auto ptr3 = mock_noref(&object3); 207 | 208 | auto res = ptr1.compare_exchange_weak(ptr2, ptr3, std::memory_order_seq_cst, std::memory_order_seq_cst); 209 | CHECK( !res ); 210 | CHECK( ptr1.load().get() == &object1 ); 211 | CHECK( ptr2.get() == &object1 ); 212 | CHECK( ptr3.get() == &object3 ); 213 | CHECK( object1.count == 2 ); 214 | CHECK( object2.count == -1 ); 215 | CHECK( object3.count == 1 ); 216 | 217 | res = ptr1.compare_exchange_weak(ptr2, ptr3, std::memory_order_seq_cst, std::memory_order_seq_cst); 218 | CHECK( res ); 219 | CHECK( ptr1.load().get() == &object3 ); 220 | CHECK( ptr2.get() == &object1 ); 221 | CHECK( ptr3.get() == &object3 ); 222 | CHECK( object1.count == 1 ); 223 | CHECK( object3.count == 2 ); 224 | } 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /test/test_com_ptr.cpp: -------------------------------------------------------------------------------- 1 | 2 | #if !ISPTR_USE_MODULES 3 | #include 4 | #endif 5 | 6 | #include 7 | 8 | #if ISPTR_USE_MODULES 9 | #ifdef _WIN32 10 | #include 11 | #endif 12 | import isptr; 13 | #endif 14 | 15 | #ifdef _WIN32 16 | 17 | using namespace isptr; 18 | 19 | TEST_SUITE("com") { 20 | 21 | TEST_CASE( "COM Ptr") { 22 | 23 | com_shared_ptr pStream; 24 | #if ISPTR_SUPPORT_OUT_PTR 25 | HRESULT res = CreateStreamOnHGlobal(nullptr, true, std::out_ptr(pStream)); 26 | #else 27 | HRESULT res = CreateStreamOnHGlobal(nullptr, true, pStream.get_output_param()); 28 | #endif 29 | CHECK( SUCCEEDED(res) ); 30 | CHECK( pStream ); 31 | } 32 | 33 | } 34 | 35 | #endif 36 | 37 | -------------------------------------------------------------------------------- /test/test_delegating_traits.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #include 4 | #endif 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #if ISPTR_USE_MODULES 13 | import isptr; 14 | #endif 15 | 16 | using namespace isptr; 17 | 18 | 19 | namespace 20 | { 21 | template 22 | class ref_counted_delegating_traits; 23 | 24 | template 25 | struct ref_counted_weak_delegating_traits 26 | { 27 | using strong_value_type = Inner; 28 | using strong_ptr_traits = ref_counted_delegating_traits; 29 | 30 | static void add_ref(const void * ref) noexcept 31 | { real_weak_from_delegating_weak(ref)->add_ref(); } 32 | 33 | static void sub_ref(const void * ref) noexcept 34 | { real_weak_from_delegating_weak(ref)->sub_ref(); } 35 | 36 | static const Inner * lock(const void * ref) noexcept 37 | { 38 | auto outer_strong = real_weak_from_delegating_weak(ref)->lock_owner(); 39 | return outer_strong ? delegating_from_real(outer_strong) : nullptr; 40 | } 41 | 42 | static Inner * lock(void * ref) noexcept 43 | { 44 | auto outer_strong = real_weak_from_delegating_weak(ref)->lock_owner(); 45 | return outer_strong ? delegating_from_real(outer_strong) : nullptr; 46 | } 47 | private: 48 | static T * real_from_delegating(Inner * pinner) 49 | { return const_cast(Converter::real_from_delegating(const_cast(pinner))); } 50 | static const T * real_from_delegating(const Inner * pinner) 51 | { return const_cast(Converter::real_from_delegating(pinner)); } 52 | 53 | static Inner * delegating_from_real(T * pouter) 54 | { return const_cast(Converter::delegating_from_real(const_cast(pouter))); } 55 | static const Inner * delegating_from_real(const T * pouter) 56 | { return const_cast(Converter::delegating_from_real(pouter)); } 57 | 58 | static typename T::weak_value_type * real_weak_from_delegating_weak(void * ref) 59 | { return static_cast(ref); } 60 | static const typename T::weak_value_type * real_weak_from_delegating_weak(const void * ref) 61 | { return static_cast(ref); } 62 | }; 63 | 64 | template 65 | class ref_counted_delegating_traits 66 | { 67 | public: 68 | using weak_value_type = void; 69 | 70 | public: 71 | using weak_ptr_traits = std::conditional_t, void>; 72 | 73 | static void add_ref(const Inner * pinner) noexcept 74 | { real_from_delegating(pinner)->add_ref(); } 75 | 76 | static void sub_ref(const Inner * pinner) noexcept 77 | { real_from_delegating(pinner)->sub_ref(); } 78 | 79 | static const void * get_weak_value(const Inner * pinner) 80 | { return real_from_delegating(pinner)->get_weak_value(); } 81 | 82 | static void * get_weak_value(Inner * pinner) 83 | { return const_cast(ref_counted_delegating_traits::get_weak_value(const_cast(pinner))); } 84 | 85 | private: 86 | static T * real_from_delegating(Inner * pinner) 87 | { return const_cast(Converter::real_from_delegating(const_cast(pinner))); } 88 | static const T * real_from_delegating(const Inner * pinner) 89 | { return const_cast(Converter::real_from_delegating(pinner)); } 90 | }; 91 | 92 | template 93 | inline 94 | intrusive_shared_ptr> 95 | weak_cast(const intrusive_shared_ptr> & src) 96 | { 97 | using dst_type = intrusive_shared_ptr>; 98 | return dst_type::noref(ref_counted_delegating_traits::get_weak_value(src.get())); 99 | } 100 | 101 | template 102 | inline 103 | intrusive_shared_ptr> 104 | weak_cast(const intrusive_shared_ptr> & src) 105 | { 106 | using dst_type = intrusive_shared_ptr>; 107 | return dst_type::noref(ref_counted_delegating_traits::get_weak_value(src.get())); 108 | } 109 | 110 | template 111 | inline 112 | intrusive_shared_ptr> 113 | strong_cast(const intrusive_shared_ptr> & src) noexcept 114 | { 115 | using dst_type = intrusive_shared_ptr>; 116 | return dst_type::noref(ref_counted_weak_delegating_traits::lock(src.get())); 117 | } 118 | 119 | template 120 | inline 121 | intrusive_shared_ptr> 122 | strong_cast(const intrusive_shared_ptr> & src) noexcept 123 | { 124 | using dst_type = intrusive_shared_ptr>; 125 | return dst_type::noref(ref_counted_weak_delegating_traits::lock(src.get())); 126 | } 127 | 128 | class outer : public ref_counted 129 | { 130 | friend ref_counted; 131 | private: 132 | struct inner_converter 133 | { 134 | static const outer * real_from_delegating(const int * pinner) noexcept 135 | { 136 | outer * dummy = nullptr; 137 | size_t distance = (uintptr_t)&(dummy->_inner) - (uintptr_t)dummy; 138 | return (const outer *)((std::byte *)pinner - distance); 139 | //return offsetof(outer, _inner); 140 | } 141 | }; 142 | 143 | using inner_traits = ref_counted_delegating_traits; 144 | friend ref_counted_delegating_traits; 145 | public: 146 | using inner_ptr = intrusive_shared_ptr; 147 | using const_inner_ptr = intrusive_shared_ptr; 148 | 149 | inner_ptr get_inner_ptr() noexcept 150 | { 151 | return inner_ptr::ref(&_inner); 152 | } 153 | const_inner_ptr get_inner_ptr() const noexcept 154 | { 155 | return const_inner_ptr::ref(&_inner); 156 | } 157 | 158 | int & inner() 159 | { return _inner; } 160 | private: 161 | int _inner = 0; 162 | }; 163 | 164 | class weak_outer : public ref_counted 165 | { 166 | friend ref_counted; 167 | private: 168 | struct inner_converter 169 | { 170 | static const weak_outer * real_from_delegating(const int * pinner) noexcept 171 | { 172 | weak_outer * dummy = nullptr; 173 | size_t distance = (uintptr_t)&(dummy->_inner) - (uintptr_t)dummy; 174 | return (const weak_outer *)((std::byte *)pinner - distance); 175 | //return offsetof(outer, _inner); 176 | } 177 | static const int * delegating_from_real(const weak_outer * pouter) noexcept 178 | { return &pouter->_inner; } 179 | }; 180 | 181 | using inner_traits = ref_counted_delegating_traits; 182 | friend ref_counted_delegating_traits; 183 | 184 | 185 | public: 186 | using inner_ptr = intrusive_shared_ptr; 187 | using const_inner_ptr = intrusive_shared_ptr; 188 | 189 | using weak_inner_ptr = intrusive_shared_ptr; 190 | using const_weak_inner_ptr = intrusive_shared_ptr; 191 | 192 | class weak_value_type : public weak_reference 193 | { 194 | friend weak_outer; 195 | friend ref_counted_weak_delegating_traits; 196 | private: 197 | weak_value_type(intptr_t count, weak_outer * owner): weak_reference(count, owner) 198 | {}; 199 | }; 200 | 201 | inner_ptr get_inner_ptr() noexcept 202 | { 203 | return inner_ptr::ref(&_inner); 204 | } 205 | const_inner_ptr get_inner_ptr() const noexcept 206 | { 207 | return const_inner_ptr::ref(&_inner); 208 | } 209 | 210 | weak_inner_ptr get_weak_inner_ptr() 211 | { 212 | return weak_inner_ptr::noref(inner_traits::get_weak_value(&_inner)); 213 | } 214 | 215 | const_weak_inner_ptr get_weak_inner_ptr() const 216 | { 217 | return const_weak_inner_ptr::noref(inner_traits::get_weak_value(&_inner)); 218 | } 219 | 220 | weak_value_type * make_weak_reference(intptr_t count) const 221 | { 222 | return new weak_value_type(count, const_cast(this)); 223 | } 224 | 225 | int & inner() 226 | { return _inner; } 227 | private: 228 | int _inner = 0; 229 | }; 230 | } 231 | 232 | TEST_SUITE("inner_ref_counted") { 233 | 234 | TEST_CASE( "Inner counting" ) { 235 | 236 | auto pouter = refcnt_attach(new outer()); 237 | auto pinner = pouter->get_inner_ptr(); 238 | CHECK(pinner); 239 | *pinner = 3; 240 | CHECK(pouter->inner() == 3); 241 | } 242 | 243 | TEST_CASE( "Weak inner counting" ) { 244 | 245 | SUBCASE( "Non const" ) { 246 | auto pouter = refcnt_attach(new weak_outer()); 247 | auto pinner1 = pouter->get_inner_ptr(); 248 | CHECK(pinner1); 249 | auto weak1 = pouter->get_weak_inner_ptr(); 250 | CHECK(weak1); 251 | auto weak2 = weak_cast(pinner1); 252 | CHECK(weak1 == weak2); 253 | 254 | auto pinner2 = strong_cast(weak2); 255 | CHECK(pinner2 == pinner1); 256 | } 257 | 258 | SUBCASE( "Const" ) { 259 | auto pouter = refcnt_attach(const_cast(new weak_outer())); 260 | auto pinner1 = pouter->get_inner_ptr(); 261 | CHECK(pinner1); 262 | auto weak1 = pouter->get_weak_inner_ptr(); 263 | CHECK(weak1); 264 | auto weak2 = weak_cast(pinner1); 265 | CHECK(weak1 == weak2); 266 | 267 | auto pinner2 = strong_cast(weak2); 268 | CHECK(pinner2 == pinner1); 269 | } 270 | } 271 | 272 | } 273 | -------------------------------------------------------------------------------- /test/test_general.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #endif 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #if ISPTR_USE_MODULES 12 | import isptr; 13 | #endif 14 | 15 | #include "mocks.h" 16 | 17 | using namespace isptr; 18 | 19 | TEST_SUITE("traits") { 20 | 21 | TEST_CASE( "Type traits are correct" ) { 22 | 23 | using ptr = mock_ptr>; 24 | 25 | SUBCASE("Construction, destruction and assignment") { 26 | 27 | CHECK( sizeof(ptr) == sizeof(instrumented_counted<1> *) ); 28 | CHECK( std::alignment_of_v == std::alignment_of_v *> ); 29 | 30 | CHECK( std::is_default_constructible_v ); 31 | CHECK( std::is_nothrow_default_constructible_v ); 32 | CHECK( !std::is_trivially_default_constructible_v ); 33 | 34 | CHECK( std::is_copy_constructible_v ); 35 | CHECK( !std::is_trivially_copy_constructible_v ); 36 | CHECK( std::is_nothrow_copy_constructible_v ); 37 | 38 | CHECK( std::is_move_constructible_v ); 39 | CHECK( !std::is_trivially_move_constructible_v ); 40 | CHECK( std::is_nothrow_move_constructible_v ); 41 | 42 | CHECK( std::is_copy_assignable_v ); 43 | CHECK( !std::is_trivially_copy_assignable_v ); 44 | CHECK( std::is_nothrow_copy_assignable_v ); 45 | 46 | CHECK( std::is_move_assignable_v ); 47 | CHECK( !std::is_trivially_move_assignable_v ); 48 | CHECK( std::is_nothrow_move_assignable_v ); 49 | 50 | CHECK( std::is_swappable_v ); 51 | CHECK( std::is_nothrow_swappable_v ); 52 | 53 | CHECK( std::is_destructible_v ); 54 | CHECK( !std::is_trivially_destructible_v ); 55 | CHECK( std::is_nothrow_destructible_v ); 56 | 57 | CHECK( std::is_constructible_v ); 58 | CHECK( std::is_nothrow_constructible_v ); 59 | CHECK( !std::is_trivially_constructible_v ); 60 | } 61 | 62 | 63 | using another_ptr = mock_ptr>; 64 | 65 | SUBCASE( "Conversions to/from other ptr types" ) { 66 | 67 | CHECK( std::is_convertible_v ); 68 | 69 | CHECK( std::is_constructible_v ); 70 | CHECK( !std::is_trivially_constructible_v ); 71 | CHECK( std::is_nothrow_constructible_v ); 72 | 73 | CHECK( std::is_assignable_v ); 74 | CHECK( !std::is_trivially_assignable_v ); 75 | CHECK( std::is_nothrow_assignable_v ); 76 | 77 | CHECK( !std::is_convertible_v ); 78 | CHECK( !std::is_constructible_v ); 79 | CHECK( !std::is_assignable_v ); 80 | } 81 | 82 | using ptr_different_traits = mock_ptr_different_traits>; 83 | SUBCASE( "Conversions to/from other traits" ) { 84 | 85 | CHECK( std::is_convertible_v ); 86 | CHECK( std::is_convertible_v ); 87 | 88 | CHECK( std::is_constructible_v ); 89 | CHECK( !std::is_trivially_constructible_v ); 90 | CHECK( std::is_nothrow_constructible_v ); 91 | 92 | CHECK( std::is_assignable_v ); 93 | CHECK( !std::is_trivially_assignable_v ); 94 | CHECK( std::is_nothrow_assignable_v ); 95 | } 96 | } 97 | 98 | using IncompatibleTypes = std::tuple *, 109 | mock_ptr> 110 | >; 111 | 112 | TEST_CASE_TEMPLATE_DEFINE( "Conversion and assignment from other types", TestType, conv_assign_incompat) { 113 | 114 | using ptr = mock_ptr>; 115 | 116 | CHECK( !std::is_constructible_v ); 117 | CHECK( !std::is_assignable_v ); 118 | } 119 | TEST_CASE_TEMPLATE_APPLY(conv_assign_incompat, IncompatibleTypes); 120 | 121 | } 122 | 123 | TEST_SUITE("empty") { 124 | 125 | TEST_CASE( "Default constructed and constructed from nullptr ptr behaves like nullptr " ) { 126 | 127 | mock_ptr> empty; 128 | 129 | SUBCASE("Default constructed") { 130 | 131 | CHECK(empty.get() == nullptr); 132 | CHECK(!bool(empty)); 133 | CHECK(empty.operator->() == nullptr); 134 | } 135 | 136 | SUBCASE("Construsted from nullptr") { 137 | mock_ptr> empty1(nullptr); 138 | 139 | CHECK(empty1.get() == nullptr); 140 | CHECK(!bool(empty1)); 141 | CHECK(empty1.operator->() == nullptr); 142 | } 143 | 144 | SUBCASE( "Comparing to another empty ptr" ) { 145 | mock_ptr> empty1; 146 | 147 | CHECK(empty == empty1); 148 | CHECK(!(empty != empty1)); 149 | CHECK(!(empty < empty1)); 150 | CHECK(empty <= empty1); 151 | CHECK(!(empty > empty1)); 152 | CHECK(empty >= empty1); 153 | 154 | } 155 | 156 | SUBCASE( "Comparing to a ptr created from nullptr" ) { 157 | 158 | mock_ptr> empty1(nullptr); 159 | 160 | CHECK(empty == empty1); 161 | CHECK(!(empty != empty1)); 162 | CHECK(!(empty < empty1)); 163 | CHECK(empty <= empty1); 164 | CHECK(!(empty > empty1)); 165 | CHECK(empty >= empty1); 166 | } 167 | 168 | SUBCASE( "Comparing to nullptr" ) { 169 | 170 | CHECK(empty == nullptr); 171 | CHECK(nullptr == empty ); 172 | CHECK(!(empty != nullptr)); 173 | CHECK(!(nullptr != empty)); 174 | } 175 | 176 | SUBCASE( "Comparing to raw pointer" ) { 177 | 178 | instrumented_counted<> * raw = nullptr; 179 | 180 | CHECK(empty == raw); 181 | CHECK(raw == empty ); 182 | CHECK(!(empty != raw)); 183 | CHECK(!(raw != empty)); 184 | CHECK(!(empty < raw)); 185 | CHECK(!(raw < empty)); 186 | CHECK(empty <= raw); 187 | CHECK(raw <= empty); 188 | CHECK(!(empty > raw)); 189 | CHECK(!(raw > empty)); 190 | CHECK(empty >= raw); 191 | CHECK(raw >= empty); 192 | } 193 | 194 | SUBCASE( "Comparing to void pointer" ) { 195 | 196 | void * raw = nullptr; 197 | 198 | CHECK(empty == raw); 199 | CHECK(raw == empty ); 200 | CHECK(!(empty != raw)); 201 | CHECK(!(raw != empty)); 202 | CHECK(!(empty < raw)); 203 | CHECK(!(raw < empty)); 204 | CHECK(empty <= raw); 205 | CHECK(raw <= empty); 206 | CHECK(!(empty > raw)); 207 | CHECK(!(raw > empty)); 208 | CHECK(empty >= raw); 209 | CHECK(raw >= empty); 210 | } 211 | 212 | SUBCASE( "Comparing to different traits" ) { 213 | 214 | mock_ptr_different_traits> empty1; 215 | 216 | CHECK(empty == empty1); 217 | CHECK(!(empty != empty1)); 218 | CHECK(!(empty < empty1)); 219 | CHECK(empty <= empty1); 220 | CHECK(!(empty > empty1)); 221 | CHECK(empty >= empty1); 222 | } 223 | } 224 | 225 | } 226 | 227 | TEST_SUITE("counting") { 228 | 229 | TEST_CASE( "Basic counting " ) { 230 | 231 | SUBCASE( "Attach" ) { 232 | 233 | instrumented_counted<> object; 234 | auto ptr = mock_noref(&object); 235 | REQUIRE( &object == ptr.get() ); 236 | REQUIRE( object.count == 1 ); 237 | CHECK(bool(ptr)); 238 | CHECK(ptr.operator->() == &object); 239 | } 240 | 241 | SUBCASE( "Ref" ) { 242 | 243 | instrumented_counted<> object; 244 | auto ptr = mock_ref(&object); 245 | REQUIRE( &object == ptr.get() ); 246 | REQUIRE( object.count == 2 ); 247 | CHECK(bool(ptr)); 248 | CHECK(ptr.operator->() == &object); 249 | 250 | mock_traits<>::sub_ref(&object); 251 | } 252 | 253 | SUBCASE( "Release" ) { 254 | 255 | instrumented_counted<> object; 256 | auto ptr = mock_noref(&object); 257 | REQUIRE( object.count == 1 ); 258 | auto p = ptr.release(); 259 | CHECK( p == &object ); 260 | CHECK(ptr.get() == nullptr); 261 | CHECK(!bool(ptr)); 262 | CHECK(ptr.operator->() == nullptr); 263 | 264 | mock_traits<>::sub_ref(&object); 265 | } 266 | 267 | SUBCASE( "Reset" ) { 268 | 269 | instrumented_counted<> object; 270 | auto ptr = mock_noref(&object); 271 | REQUIRE( object.count == 1 ); 272 | ptr.reset(); 273 | CHECK(ptr.get() == nullptr); 274 | CHECK(!bool(ptr)); 275 | CHECK(ptr.operator->() == nullptr); 276 | } 277 | 278 | SUBCASE( "Copy and assignment" ) { 279 | 280 | instrumented_counted<> object; 281 | auto ptr = mock_noref(&object); 282 | REQUIRE( object.count == 1 ); 283 | 284 | auto ptr2(ptr); 285 | REQUIRE( object.count == 2 ); 286 | 287 | auto ptr3(mock_ref(&object)); 288 | REQUIRE( object.count == 3 ); 289 | 290 | mock_ptr> ptr4; 291 | ptr4 = ptr; 292 | REQUIRE( object.count == 4 ); 293 | 294 | mock_ptr> ptr5; 295 | ptr5 = mock_ref(&object); 296 | REQUIRE( object.count == 5 ); 297 | } 298 | 299 | SUBCASE( "Self assignment" ) { 300 | 301 | instrumented_counted<> object; 302 | auto ptr = mock_noref(&object); 303 | 304 | ptr = ptr; 305 | REQUIRE( object.count == 1 ); 306 | 307 | ptr = std::move(ptr); 308 | REQUIRE( ptr.get() == &object ); 309 | REQUIRE( object.count == 1 ); 310 | } 311 | 312 | SUBCASE( " Swap ") { 313 | 314 | instrumented_counted<> object1, object2; 315 | auto ptr1 = mock_noref(&object1); 316 | auto ptr2 = mock_noref(&object2); 317 | swap(ptr1, ptr2); 318 | CHECK( ptr1.get() == &object2 ); 319 | CHECK( ptr2.get() == &object1 ); 320 | REQUIRE( object1.count == 1 ); 321 | REQUIRE( object2.count == 1 ); 322 | 323 | ptr1.swap(ptr2); 324 | CHECK( ptr1.get() == &object1 ); 325 | CHECK( ptr2.get() == &object2 ); 326 | REQUIRE( object1.count == 1 ); 327 | REQUIRE( object2.count == 1 ); 328 | 329 | ptr2.swap(ptr1); 330 | CHECK( ptr1.get() == &object2 ); 331 | CHECK( ptr2.get() == &object1 ); 332 | REQUIRE( object1.count == 1 ); 333 | REQUIRE( object2.count == 1 ); 334 | } 335 | 336 | } 337 | 338 | TEST_CASE( "Conversions " ) { 339 | 340 | SUBCASE( "Attach" ) { 341 | 342 | derived_instrumented_counted<> object; 343 | mock_ptr> ptr = mock_noref(&object); 344 | REQUIRE( &object == ptr.get() ); 345 | REQUIRE( object.count == 1 ); 346 | CHECK(bool(ptr)); 347 | CHECK(ptr.operator->() == &object); 348 | } 349 | 350 | SUBCASE( "Ref" ) { 351 | 352 | derived_instrumented_counted<> object; 353 | mock_ptr> ptr = mock_ref(&object); 354 | REQUIRE( &object == ptr.get() ); 355 | REQUIRE( object.count == 2 ); 356 | CHECK(bool(ptr)); 357 | CHECK(ptr.operator->() == &object); 358 | 359 | mock_traits<>::sub_ref(&object); 360 | } 361 | 362 | SUBCASE( "Release" ) { 363 | 364 | derived_instrumented_counted<> object; 365 | mock_ptr> ptr = mock_noref(&object); 366 | REQUIRE( object.count == 1 ); 367 | auto p = ptr.release(); 368 | CHECK( p == &object ); 369 | CHECK(ptr.get() == nullptr); 370 | CHECK(!bool(ptr)); 371 | CHECK(ptr.operator->() == nullptr); 372 | 373 | mock_traits<>::sub_ref(&object); 374 | } 375 | 376 | SUBCASE( "Reset" ) { 377 | 378 | derived_instrumented_counted<> object; 379 | mock_ptr> ptr = mock_noref(&object); 380 | REQUIRE( object.count == 1 ); 381 | ptr.reset(); 382 | CHECK(ptr.get() == nullptr); 383 | CHECK(!bool(ptr)); 384 | CHECK(ptr.operator->() == nullptr); 385 | } 386 | 387 | SUBCASE( "Copy and assignment" ) { 388 | 389 | derived_instrumented_counted<> object; 390 | auto ptr = mock_noref(&object); 391 | REQUIRE( object.count == 1 ); 392 | 393 | mock_ptr> ptr2(ptr); 394 | REQUIRE( object.count == 2 ); 395 | 396 | mock_ptr> ptr3(mock_ref(&object)); 397 | REQUIRE( object.count == 3 ); 398 | 399 | mock_ptr> ptr4; 400 | ptr4 = ptr; 401 | REQUIRE( object.count == 4 ); 402 | 403 | mock_ptr> ptr5; 404 | ptr5 = mock_ref(&object); 405 | REQUIRE( object.count == 5 ); 406 | } 407 | 408 | } 409 | 410 | TEST_CASE( "Different traits " ) { 411 | 412 | SUBCASE( "Copy" ) { 413 | 414 | instrumented_counted<> object; 415 | mock_ptr_different_traits> ptr = mock_noref_different_traits(&object); 416 | mock_ptr> ptr1(ptr); 417 | REQUIRE( &object == ptr1.get() ); 418 | REQUIRE( object.count == 2 ); 419 | } 420 | 421 | SUBCASE( "Move" ) { 422 | 423 | instrumented_counted<> object; 424 | mock_ptr_different_traits> ptr = mock_noref_different_traits(&object); 425 | mock_ptr> ptr1(std::move(ptr)); 426 | REQUIRE( &object == ptr1.get() ); 427 | REQUIRE( object.count == 1 ); 428 | } 429 | } 430 | 431 | TEST_CASE( "Casts" ) { 432 | 433 | derived_instrumented_counted<> object; 434 | const instrumented_counted<> const_object; 435 | derived_instrumented_counted<> derived_object; 436 | 437 | mock_ptr> ptr = mock_noref(&object); 438 | auto ptr_const = mock_noref(&const_object); 439 | auto ptr_derived = mock_noref(&derived_object); 440 | 441 | SUBCASE( "Const cast" ) { 442 | 443 | auto res = intrusive_const_cast>>(ptr_const); 444 | CHECK( res.get() == &const_object ); 445 | CHECK( const_object.count == 2 ); 446 | 447 | res = intrusive_const_cast>>(mock_ref(&const_object)); 448 | CHECK( res.get() == &const_object ); 449 | CHECK( const_object.count == 2 ); 450 | } 451 | 452 | SUBCASE( "Dynamic cast" ) { 453 | 454 | auto res = intrusive_dynamic_cast>>(ptr); 455 | CHECK( res.get() == &object ); 456 | CHECK( object.count == 2 ); 457 | 458 | res = intrusive_dynamic_cast>>(mock_ptr>(ptr)); 459 | CHECK( res.get() == &object ); 460 | CHECK( object.count == 2 ); 461 | 462 | auto res1 = intrusive_dynamic_cast>>(ptr_const); 463 | CHECK( !res1 ); 464 | 465 | res1 = intrusive_dynamic_cast>>(mock_ref(&const_object)); 466 | CHECK( !res1 ); 467 | } 468 | 469 | SUBCASE( "Static cast" ) { 470 | 471 | auto res = intrusive_static_cast>>(ptr); 472 | CHECK( res.get() == &object ); 473 | CHECK( object.count == 2 ); 474 | 475 | res = intrusive_static_cast>>(mock_ptr>(ptr)); 476 | CHECK( res.get() == &object ); 477 | CHECK( object.count == 2 ); 478 | } 479 | 480 | } 481 | 482 | } 483 | 484 | TEST_SUITE("output") { 485 | 486 | TEST_CASE( "Output" ) { 487 | 488 | instrumented_counted<> object; 489 | auto ptr = mock_noref(&object); 490 | 491 | std::ostringstream str1; 492 | 493 | auto & res = (str1 << ptr); 494 | CHECK( &res == &str1 ); 495 | 496 | std::ostringstream str2; 497 | str2 << &object; 498 | 499 | CHECK( str1.str() == str2.str() ); 500 | } 501 | 502 | #if ISPTR_SUPPORT_STD_FORMAT 503 | 504 | TEST_CASE( "Format" ) { 505 | 506 | instrumented_counted<> object; 507 | auto ptr = mock_noref(&object); 508 | 509 | std::string res = std::format("{}", ptr); 510 | std::string expected = std::format("{}", (void *)ptr.get()); 511 | 512 | CHECK(res == expected); 513 | } 514 | 515 | #endif 516 | 517 | } 518 | 519 | TEST_SUITE("output param") { 520 | 521 | TEST_CASE( "Output param" ) { 522 | 523 | instrumented_counted<> object, object1; 524 | auto ptr = mock_noref(&object); 525 | auto ptr1 = mock_noref(&object1); 526 | 527 | auto func = [&object1] (instrumented_counted<> ** out) { 528 | mock_traits<>::add_ref(&object1); 529 | *out = &object1; 530 | }; 531 | 532 | func(ptr.get_output_param()); 533 | CHECK( ptr.get() == &object1 ); 534 | CHECK( object.count == -1 ); 535 | CHECK( object1.count == 2 ); 536 | } 537 | 538 | TEST_CASE( "Input Output param" ) { 539 | 540 | instrumented_counted<> object, object1; 541 | auto ptr = mock_noref(&object); 542 | auto ptr1 = mock_noref(&object1); 543 | 544 | auto func = [&] (instrumented_counted<> ** inout) { 545 | CHECK(*inout == &object); 546 | mock_traits<>::sub_ref(&object); 547 | mock_traits<>::add_ref(&object1); 548 | *inout = &object1; 549 | }; 550 | 551 | func(ptr.get_inout_param()); 552 | CHECK( ptr.get() == &object1 ); 553 | CHECK( object.count == -1 ); 554 | CHECK( object1.count == 2 ); 555 | } 556 | 557 | TEST_CASE( "Member pointer" ) { 558 | 559 | instrumented_counted<> object; 560 | auto ptr = mock_noref(&object); 561 | 562 | int instrumented_counted<>::*pcount = &instrumented_counted<>::count; 563 | 564 | auto x = ptr->*pcount; 565 | CHECK(x == 1); 566 | } 567 | 568 | TEST_CASE( "Hash code" ) { 569 | 570 | instrumented_counted<> object; 571 | auto ptr = mock_noref(&object); 572 | 573 | CHECK(std::hash()(ptr) == std::hash *>()(&object)); 574 | CHECK(std::hash()(ptr) == hash_value(ptr)); 575 | } 576 | 577 | } 578 | -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT 2 | 3 | #include 4 | 5 | #if ISPTR_USE_PYTHON 6 | #include 7 | #endif 8 | 9 | int main(int argc, char** argv) 10 | { 11 | #if ISPTR_USE_PYTHON 12 | Py_Initialize(); 13 | #endif 14 | int ret = doctest::Context(argc, argv).run(); 15 | #if ISPTR_USE_PYTHON 16 | if (Py_FinalizeEx() < 0) { 17 | return 120; 18 | } 19 | #endif 20 | return ret; 21 | } 22 | -------------------------------------------------------------------------------- /test/test_out_ptr.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #endif 4 | 5 | #include 6 | 7 | #if ISPTR_USE_MODULES 8 | import isptr; 9 | #endif 10 | 11 | #include "mocks.h" 12 | 13 | using namespace isptr; 14 | 15 | #if ISPTR_SUPPORT_OUT_PTR 16 | 17 | TEST_SUITE("out_ptr") { 18 | 19 | TEST_CASE( "out_ptr basics" ) { 20 | 21 | instrumented_counted<> items[2] = {}; 22 | auto c_func = [&](size_t idx, instrumented_counted<> ** res) { 23 | CHECK(*res == nullptr); 24 | *res = &items[idx]; 25 | }; 26 | 27 | mock_ptr> p; 28 | 29 | c_func(0, std::out_ptr(p)); 30 | CHECK(p.get() == &items[0]); 31 | CHECK(items[0].count == 1); 32 | 33 | c_func(1, std::out_ptr(p)); 34 | CHECK(p.get() == &items[1]); 35 | CHECK(items[0].count == -1); 36 | CHECK(items[1].count == 1); 37 | } 38 | 39 | TEST_CASE( "out_ptr void_pp" ) { 40 | 41 | instrumented_counted<> items[2] = {}; 42 | auto c_func = [&](size_t idx, void ** res) { 43 | CHECK(*res == nullptr); 44 | *res = &items[idx]; 45 | }; 46 | 47 | mock_ptr> p; 48 | 49 | c_func(0, std::out_ptr(p)); 50 | CHECK(p.get() == &items[0]); 51 | CHECK(items[0].count == 1); 52 | 53 | c_func(1, std::out_ptr(p)); 54 | CHECK(p.get() == &items[1]); 55 | CHECK(items[0].count == -1); 56 | CHECK(items[1].count == 1); 57 | } 58 | 59 | TEST_CASE( "inout_ptr basics" ) { 60 | 61 | instrumented_counted<> items[2] = {}; 62 | auto c_func = [&](size_t old, size_t idx, instrumented_counted<> ** res) { 63 | if (old == size_t(-1)) 64 | CHECK(*res == nullptr); 65 | else 66 | CHECK(*res == &items[old]); 67 | if (*res) 68 | mock_traits<>::sub_ref(*res); 69 | *res = &items[idx]; 70 | }; 71 | 72 | mock_ptr> p; 73 | 74 | c_func(size_t(-1), 0, std::inout_ptr(p)); 75 | CHECK(p.get() == &items[0]); 76 | CHECK(items[0].count == 1); 77 | 78 | c_func(0, 1, std::inout_ptr(p)); 79 | CHECK(p.get() == &items[1]); 80 | CHECK(items[0].count == -1); 81 | CHECK(items[1].count == 1); 82 | } 83 | 84 | TEST_CASE( "inout_ptr void_pp" ) { 85 | 86 | instrumented_counted<> items[2] = {}; 87 | auto c_func = [&](size_t old, size_t idx, void ** res) { 88 | auto ptr = (instrumented_counted<> **)(res); 89 | if (old == size_t(-1)) 90 | CHECK(*ptr == nullptr); 91 | else 92 | CHECK(*ptr == &items[old]); 93 | if (*ptr) 94 | mock_traits<>::sub_ref(*ptr); 95 | *ptr = &items[idx]; 96 | }; 97 | 98 | mock_ptr> p; 99 | 100 | c_func(size_t(-1), 0, std::inout_ptr(p)); 101 | CHECK(p.get() == &items[0]); 102 | CHECK(items[0].count == 1); 103 | 104 | c_func(0, 1, std::inout_ptr(p)); 105 | CHECK(p.get() == &items[1]); 106 | CHECK(items[0].count == -1); 107 | CHECK(items[1].count == 1); 108 | } 109 | 110 | } 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /test/test_python_ptr.cpp: -------------------------------------------------------------------------------- 1 | 2 | #if ISPTR_USE_PYTHON 3 | 4 | #if ISPTR_USE_MODULES 5 | #define PY_SSIZE_T_CLEAN 6 | #include 7 | 8 | #include 9 | 10 | import isptr; 11 | #else 12 | #define PY_SSIZE_T_CLEAN 13 | #include 14 | 15 | #include 16 | #endif 17 | 18 | using namespace isptr; 19 | 20 | TEST_SUITE("python") { 21 | 22 | TEST_CASE( "Python Ptr" ) { 23 | 24 | auto str = py_attach(PyUnicode_FromString("Hello")); 25 | REQUIRE(str); 26 | CHECK( PyUnicode_GetLength(str.get()) == 5 ); 27 | } 28 | 29 | } 30 | 31 | #endif 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/test_ref_counted.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #include 4 | #endif 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #if ISPTR_USE_MODULES 12 | import isptr; 13 | #endif 14 | 15 | using namespace isptr; 16 | 17 | namespace 18 | { 19 | struct minimal_counted : ref_counted 20 | { 21 | friend ref_counted; 22 | 23 | private: 24 | ~minimal_counted() noexcept 25 | {} 26 | }; 27 | 28 | static_assert( !std::is_default_constructible_v> ); 29 | static_assert( !std::is_copy_constructible_v> ); 30 | static_assert( !std::is_move_constructible_v> ); 31 | static_assert( !std::is_copy_assignable_v> ); 32 | static_assert( !std::is_move_assignable_v> ); 33 | static_assert( !std::is_swappable_v> ); 34 | static_assert( !std::is_destructible_v> ); 35 | 36 | static_assert( sizeof(minimal_counted) == sizeof(char) ); 37 | static_assert( !std::is_default_constructible_v ); 38 | static_assert( !std::is_copy_constructible_v ); 39 | static_assert( !std::is_move_constructible_v ); 40 | static_assert( !std::is_copy_assignable_v ); 41 | static_assert( !std::is_move_assignable_v ); 42 | static_assert( !std::is_swappable_v ); 43 | static_assert( !std::is_destructible_v ); 44 | 45 | 46 | struct adapded { char c; }; 47 | using minimal_adapded_counted = ref_counted_adapter; 48 | 49 | static_assert( sizeof(minimal_adapded_counted) == 2 * sizeof(char) ); 50 | static_assert( !std::is_default_constructible_v ); 51 | static_assert( !std::is_copy_constructible_v ); 52 | static_assert( !std::is_move_constructible_v ); 53 | static_assert( !std::is_copy_assignable_v ); 54 | static_assert( !std::is_move_assignable_v ); 55 | static_assert( !std::is_destructible_v ); 56 | 57 | using minimal_wrapped_counted = ref_counted_wrapper; 58 | 59 | static_assert( sizeof(minimal_wrapped_counted) == 2 * sizeof(char) ); 60 | static_assert( !std::is_default_constructible_v ); 61 | static_assert( !std::is_copy_constructible_v ); 62 | static_assert( !std::is_move_constructible_v ); 63 | static_assert( !std::is_copy_assignable_v ); 64 | static_assert( !std::is_move_assignable_v ); 65 | static_assert( !std::is_destructible_v ); 66 | 67 | 68 | struct simple_counted : ref_counted 69 | { 70 | friend ref_counted; 71 | 72 | static inline int instance_count = 0; 73 | 74 | simple_counted() noexcept 75 | { ++instance_count; } 76 | 77 | simple_counted(int) 78 | { throw std::runtime_error("x"); } 79 | private: 80 | ~simple_counted() noexcept 81 | { --instance_count; } 82 | }; 83 | 84 | static_assert( !std::is_default_constructible_v ); 85 | static_assert( !std::is_copy_constructible_v ); 86 | static_assert( !std::is_move_constructible_v ); 87 | static_assert( !std::is_copy_assignable_v ); 88 | static_assert( !std::is_move_assignable_v ); 89 | static_assert( !std::is_destructible_v ); 90 | 91 | } 92 | 93 | TEST_SUITE("ref_counted") { 94 | 95 | TEST_CASE( "Minimal ref counted works" ) { 96 | 97 | SUBCASE("derived") { 98 | auto p1 = refcnt_attach(new minimal_counted()); 99 | CHECK(p1); 100 | auto p2 = p1; 101 | CHECK(p2 == p1); 102 | } 103 | 104 | SUBCASE("adapted") { 105 | auto p1 = refcnt_attach(new minimal_adapded_counted(adapded{'a'})); 106 | CHECK(p1); 107 | CHECK(p1->c == 'a'); 108 | auto p2 = p1; 109 | CHECK(p2 == p1); 110 | } 111 | 112 | SUBCASE("wrapped") { 113 | auto p1 = refcnt_attach(new minimal_wrapped_counted('a')); 114 | CHECK(p1); 115 | CHECK(p1->wrapped == 'a'); 116 | auto p2 = p1; 117 | CHECK(p2 == p1); 118 | } 119 | } 120 | 121 | TEST_CASE( "Simple ref counted works" ) { 122 | 123 | auto p1 = refcnt_attach(new simple_counted()); 124 | CHECK(simple_counted::instance_count == 1); 125 | auto p2 = p1; 126 | CHECK(simple_counted::instance_count == 1); 127 | p1.reset(); 128 | CHECK(simple_counted::instance_count == 1); 129 | p2.reset(); 130 | CHECK(simple_counted::instance_count == 0); 131 | } 132 | 133 | TEST_CASE( "Ref counted with ctor exception" ) { 134 | 135 | try 136 | { 137 | auto p1 = refcnt_attach(new simple_counted(2)); 138 | } 139 | catch(std::exception &) 140 | { 141 | CHECK(simple_counted::instance_count == 0); 142 | } 143 | } 144 | 145 | TEST_CASE( "Custom destroy" ) { 146 | 147 | 148 | struct custom_destroy : ref_counted 149 | { 150 | friend ref_counted; 151 | 152 | custom_destroy(bool * d): destroyed(d) {} 153 | 154 | bool * destroyed; 155 | private: 156 | void destroy() const noexcept 157 | { 158 | *destroyed = true; 159 | free((void*)this); 160 | } 161 | }; 162 | 163 | bool destroyed = false; 164 | auto p1 = refcnt_attach(new (malloc(sizeof(custom_destroy))) custom_destroy{&destroyed}); 165 | p1.reset(); 166 | CHECK(destroyed); 167 | 168 | } 169 | 170 | TEST_CASE( "Ref counted wrapper" ) { 171 | 172 | SUBCASE("adapter") { 173 | auto p1 = refcnt_attach(new ref_counted_adapter>(5)); 174 | CHECK( p1->size() == 5 ); 175 | } 176 | SUBCASE("wrapper") { 177 | auto p1 = refcnt_attach(new ref_counted_wrapper>(5)); 178 | CHECK( p1->wrapped.size() == 5 ); 179 | } 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /test/test_ref_counted_st.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #include 4 | #endif 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #if ISPTR_USE_MODULES 12 | import isptr; 13 | #endif 14 | 15 | using namespace isptr; 16 | 17 | namespace 18 | { 19 | struct minimal_counted : ref_counted 20 | { 21 | friend ref_counted; 22 | 23 | private: 24 | ~minimal_counted() noexcept 25 | {} 26 | }; 27 | 28 | static_assert( !std::is_default_constructible_v> ); 29 | static_assert( !std::is_copy_constructible_v> ); 30 | static_assert( !std::is_move_constructible_v> ); 31 | static_assert( !std::is_copy_assignable_v> ); 32 | static_assert( !std::is_move_assignable_v> ); 33 | static_assert( !std::is_swappable_v> ); 34 | static_assert( !std::is_destructible_v> ); 35 | 36 | static_assert( sizeof(minimal_counted) == sizeof(char) ); 37 | static_assert( !std::is_default_constructible_v ); 38 | static_assert( !std::is_copy_constructible_v ); 39 | static_assert( !std::is_move_constructible_v ); 40 | static_assert( !std::is_copy_assignable_v ); 41 | static_assert( !std::is_move_assignable_v ); 42 | static_assert( !std::is_swappable_v ); 43 | static_assert( !std::is_destructible_v ); 44 | 45 | 46 | struct adapded { char c; }; 47 | using minimal_adapded_counted = ref_counted_adapter; 48 | 49 | static_assert( sizeof(minimal_adapded_counted) == 2 * sizeof(char) ); 50 | static_assert( !std::is_default_constructible_v ); 51 | static_assert( !std::is_copy_constructible_v ); 52 | static_assert( !std::is_move_constructible_v ); 53 | static_assert( !std::is_copy_assignable_v ); 54 | static_assert( !std::is_move_assignable_v ); 55 | static_assert( !std::is_destructible_v ); 56 | 57 | using minimal_wrapped_counted = ref_counted_wrapper; 58 | 59 | static_assert( sizeof(minimal_wrapped_counted) == 2 * sizeof(char) ); 60 | static_assert( !std::is_default_constructible_v ); 61 | static_assert( !std::is_copy_constructible_v ); 62 | static_assert( !std::is_move_constructible_v ); 63 | static_assert( !std::is_copy_assignable_v ); 64 | static_assert( !std::is_move_assignable_v ); 65 | static_assert( !std::is_destructible_v ); 66 | 67 | 68 | struct simple_counted : ref_counted_st 69 | { 70 | friend ref_counted; 71 | 72 | static inline int instance_count = 0; 73 | 74 | simple_counted() noexcept 75 | { ++instance_count; } 76 | 77 | simple_counted(int) 78 | { throw std::runtime_error("x"); } 79 | private: 80 | ~simple_counted() noexcept 81 | { --instance_count; } 82 | }; 83 | 84 | static_assert( !std::is_default_constructible_v ); 85 | static_assert( !std::is_copy_constructible_v ); 86 | static_assert( !std::is_move_constructible_v ); 87 | static_assert( !std::is_copy_assignable_v ); 88 | static_assert( !std::is_move_assignable_v ); 89 | static_assert( !std::is_destructible_v ); 90 | 91 | } 92 | 93 | TEST_SUITE("ref_counted_st") { 94 | 95 | TEST_CASE( "Minimal st ref counted works" ) { 96 | 97 | SUBCASE("derived") { 98 | auto p1 = refcnt_attach(new minimal_counted()); 99 | CHECK(p1); 100 | auto p2 = p1; 101 | CHECK(p2 == p1); 102 | } 103 | 104 | SUBCASE("adapted") { 105 | auto p1 = refcnt_attach(new minimal_adapded_counted(adapded{'a'})); 106 | CHECK(p1); 107 | CHECK(p1->c == 'a'); 108 | auto p2 = p1; 109 | CHECK(p2 == p1); 110 | } 111 | 112 | SUBCASE("wrapped") { 113 | auto p1 = refcnt_attach(new minimal_wrapped_counted('a')); 114 | CHECK(p1); 115 | CHECK(p1->wrapped == 'a'); 116 | auto p2 = p1; 117 | CHECK(p2 == p1); 118 | } 119 | } 120 | 121 | TEST_CASE( "Simple st ref counted works" ) { 122 | 123 | auto p1 = refcnt_attach(new simple_counted()); 124 | CHECK(simple_counted::instance_count == 1); 125 | auto p2 = p1; 126 | CHECK(simple_counted::instance_count == 1); 127 | p1.reset(); 128 | CHECK(simple_counted::instance_count == 1); 129 | p2.reset(); 130 | CHECK(simple_counted::instance_count == 0); 131 | } 132 | 133 | TEST_CASE( "St ref counted with ctor exception" ) { 134 | 135 | try 136 | { 137 | auto p1 = refcnt_attach(new simple_counted(2)); 138 | } 139 | catch(std::exception &) 140 | { 141 | CHECK(simple_counted::instance_count == 0); 142 | } 143 | } 144 | 145 | TEST_CASE( "Custom destroy st" ) { 146 | 147 | 148 | struct custom_destroy : ref_counted_st 149 | { 150 | friend ref_counted; 151 | 152 | custom_destroy(bool * d): destroyed(d) {} 153 | 154 | bool * destroyed; 155 | private: 156 | void destroy() const noexcept 157 | { 158 | *destroyed = true; 159 | free((void*)this); 160 | } 161 | }; 162 | 163 | bool destroyed = false; 164 | auto p1 = refcnt_attach(new (malloc(sizeof(custom_destroy))) custom_destroy{&destroyed}); 165 | p1.reset(); 166 | CHECK(destroyed); 167 | 168 | } 169 | 170 | TEST_CASE( "St ref counted wrapper" ) { 171 | 172 | SUBCASE("adapter") { 173 | auto p1 = refcnt_attach(new ref_counted_adapter_st>(5)); 174 | CHECK( p1->size() == 5 ); 175 | } 176 | SUBCASE("wrapper") { 177 | auto p1 = refcnt_attach(new ref_counted_wrapper_st>(5)); 178 | CHECK( p1->wrapped.size() == 5 ); 179 | } 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /test/test_weak_ref_counted.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #include 4 | #endif 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if ISPTR_USE_MODULES 11 | import isptr; 12 | #endif 13 | 14 | using namespace isptr; 15 | 16 | namespace 17 | { 18 | int derived_count = 0; 19 | 20 | struct derived_counted : weak_ref_counted 21 | { 22 | friend ref_counted; 23 | public: 24 | derived_counted() noexcept 25 | { 26 | ++derived_count; 27 | } 28 | private: 29 | ~derived_counted() noexcept 30 | { 31 | auto weak = get_weak_ptr(); 32 | CHECK(weak); 33 | --derived_count; 34 | } 35 | }; 36 | 37 | 38 | int wrapped_count = 0; 39 | struct wrapped 40 | { 41 | wrapped() 42 | { 43 | ++wrapped_count; 44 | } 45 | ~wrapped() 46 | { 47 | --wrapped_count; 48 | } 49 | 50 | int value = 5; 51 | }; 52 | 53 | using wrapped_counted = weak_ref_counted_adapter; 54 | 55 | int with_custom_weak_reference_count = 0; 56 | 57 | struct custom_weak_reference; 58 | 59 | class with_custom_weak_reference : public ref_counted 60 | { 61 | friend ref_counted; 62 | 63 | public: 64 | with_custom_weak_reference() 65 | { 66 | ++with_custom_weak_reference_count; 67 | } 68 | private: 69 | 70 | ~with_custom_weak_reference() noexcept 71 | { 72 | auto weak = get_weak_ptr(); 73 | CHECK(weak); 74 | --with_custom_weak_reference_count; 75 | } 76 | 77 | custom_weak_reference * make_weak_reference(intptr_t count) const; 78 | }; 79 | 80 | struct custom_weak_reference : weak_reference 81 | { 82 | custom_weak_reference(intptr_t count, with_custom_weak_reference * obj): weak_reference(count, obj) 83 | {} 84 | 85 | ~custom_weak_reference() 86 | { 87 | CHECK(on_owner_destruction_called); 88 | CHECK(with_custom_weak_reference_count == 0); 89 | } 90 | 91 | void on_owner_destruction() const 92 | { 93 | CHECK(with_custom_weak_reference_count == 0); //owner is still alive but its refcount is 0, you cannot ressurrect! 94 | on_owner_destruction_called = true; 95 | } 96 | 97 | mutable bool on_owner_destruction_called = false; 98 | }; 99 | 100 | inline custom_weak_reference * with_custom_weak_reference::make_weak_reference(intptr_t count) const 101 | { return new custom_weak_reference(count, const_cast(this)); } 102 | } 103 | 104 | TEST_SUITE("traits") { 105 | 106 | TEST_CASE( "Weak ref counted type traits are correct" ) { 107 | 108 | SUBCASE( "Base" ) { 109 | 110 | CHECK( !std::is_default_constructible_v> ); 111 | CHECK( !std::is_copy_constructible_v> ); 112 | CHECK( !std::is_move_constructible_v> ); 113 | CHECK( !std::is_copy_assignable_v> ); 114 | CHECK( !std::is_move_assignable_v> ); 115 | CHECK( !std::is_swappable_v> ); 116 | CHECK( !std::is_destructible_v> ); 117 | } 118 | 119 | SUBCASE( "Derived" ) { 120 | CHECK( !std::is_default_constructible_v ); 121 | CHECK( !std::is_copy_constructible_v ); 122 | CHECK( !std::is_move_constructible_v ); 123 | CHECK( !std::is_copy_assignable_v ); 124 | CHECK( !std::is_move_assignable_v ); 125 | CHECK( !std::is_swappable_v ); 126 | CHECK( !std::is_destructible_v ); 127 | } 128 | 129 | SUBCASE( "Derived WeakRef" ) { 130 | 131 | CHECK( !std::is_default_constructible_v ); 132 | CHECK( !std::is_copy_constructible_v ); 133 | CHECK( !std::is_move_constructible_v ); 134 | CHECK( !std::is_copy_assignable_v ); 135 | CHECK( !std::is_move_assignable_v ); 136 | CHECK( !std::is_swappable_v ); 137 | CHECK( !std::is_destructible_v ); 138 | } 139 | 140 | SUBCASE( "Wrapped" ) { 141 | CHECK( !std::is_default_constructible_v ); 142 | CHECK( !std::is_copy_constructible_v ); 143 | CHECK( !std::is_move_constructible_v ); 144 | CHECK( !std::is_copy_assignable_v ); 145 | CHECK( !std::is_move_assignable_v ); 146 | CHECK( !std::is_destructible_v ); 147 | } 148 | 149 | SUBCASE( "Wrapped WeakRef" ) { 150 | CHECK( !std::is_default_constructible_v ); 151 | CHECK( !std::is_copy_constructible_v ); 152 | CHECK( !std::is_move_constructible_v ); 153 | CHECK( !std::is_copy_assignable_v ); 154 | CHECK( !std::is_move_assignable_v ); 155 | CHECK( !std::is_destructible_v ); 156 | } 157 | } 158 | 159 | } 160 | 161 | TEST_SUITE("ref_counted") { 162 | 163 | TEST_CASE( "Weak ref counted works" ) { 164 | 165 | SUBCASE( "Derived" ) { 166 | auto original = refcnt_attach(new derived_counted()); 167 | auto weak1 = original->get_weak_ptr(); 168 | CHECK(derived_count == 1); 169 | auto strong1 = weak1->lock(); 170 | CHECK(original == strong1); 171 | auto weak2 = strong1->get_weak_ptr(); 172 | CHECK(weak1 == weak2); 173 | auto weak3 = weak_cast(strong1); 174 | CHECK(weak1 == weak3); 175 | original.reset(); 176 | strong1.reset(); 177 | CHECK(derived_count == 0); 178 | 179 | strong1 = weak1->lock(); 180 | CHECK(!strong1); 181 | } 182 | 183 | SUBCASE( "Const Derived" ) { 184 | refcnt_ptr original = refcnt_attach(new derived_counted()); 185 | auto weak1 = original->get_weak_ptr(); 186 | CHECK(derived_count == 1); 187 | auto strong1 = weak1->lock(); 188 | CHECK(original == strong1); 189 | auto weak2 = strong1->get_weak_ptr(); 190 | CHECK(weak1 == weak2); 191 | original.reset(); 192 | strong1.reset(); 193 | CHECK(derived_count == 0); 194 | 195 | strong1 = weak1->lock(); 196 | CHECK(!strong1); 197 | strong1 = strong_cast(weak2); 198 | CHECK(!strong1); 199 | } 200 | 201 | SUBCASE( "Wrapped" ) { 202 | 203 | auto p = refcnt_attach(new wrapped_counted()); 204 | CHECK(wrapped_count == 1); 205 | CHECK(p->value == 5); 206 | p.reset(); 207 | CHECK( wrapped_count == 0 ); 208 | } 209 | 210 | SUBCASE( "Custom Weak Reference" ) { 211 | 212 | auto strong = refcnt_attach(new with_custom_weak_reference); 213 | CHECK(with_custom_weak_reference_count == 1); 214 | auto weak = strong->get_weak_ptr(); 215 | auto strong1 = weak->lock(); 216 | CHECK( strong1 == strong ); 217 | CHECK(with_custom_weak_reference_count == 1); 218 | strong.reset(); 219 | CHECK(with_custom_weak_reference_count == 1); 220 | strong1.reset(); 221 | CHECK(with_custom_weak_reference_count == 0); 222 | strong1 = weak->lock(); 223 | CHECK(!strong1); 224 | } 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /test/test_weak_ref_counted_st.cpp: -------------------------------------------------------------------------------- 1 | #if !ISPTR_USE_MODULES 2 | #include 3 | #include 4 | #endif 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if ISPTR_USE_MODULES 11 | import isptr; 12 | #endif 13 | 14 | using namespace isptr; 15 | 16 | namespace 17 | { 18 | int derived_count = 0; 19 | 20 | struct derived_counted : weak_ref_counted_st 21 | { 22 | friend ref_counted; 23 | public: 24 | derived_counted() noexcept 25 | { 26 | ++derived_count; 27 | } 28 | private: 29 | ~derived_counted() noexcept 30 | { 31 | auto weak = get_weak_ptr(); 32 | CHECK(weak); 33 | --derived_count; 34 | } 35 | }; 36 | 37 | 38 | int wrapped_count = 0; 39 | struct wrapped 40 | { 41 | wrapped() 42 | { 43 | ++wrapped_count; 44 | } 45 | ~wrapped() 46 | { 47 | --wrapped_count; 48 | } 49 | 50 | int value = 5; 51 | }; 52 | 53 | using wrapped_counted = weak_ref_counted_adapter_st; 54 | 55 | int with_custom_weak_reference_count = 0; 56 | 57 | struct custom_weak_reference; 58 | 59 | class with_custom_weak_reference : public ref_counted 62 | { 63 | friend ref_counted; 64 | 65 | public: 66 | with_custom_weak_reference() 67 | { 68 | ++with_custom_weak_reference_count; 69 | } 70 | private: 71 | 72 | ~with_custom_weak_reference() noexcept 73 | { 74 | auto weak = get_weak_ptr(); 75 | CHECK(weak); 76 | --with_custom_weak_reference_count; 77 | } 78 | 79 | custom_weak_reference * make_weak_reference(intptr_t count) const; 80 | }; 81 | 82 | struct custom_weak_reference : weak_reference 83 | { 84 | custom_weak_reference(intptr_t count, with_custom_weak_reference * obj): weak_reference(count, obj) 85 | {} 86 | 87 | ~custom_weak_reference() 88 | { 89 | CHECK(on_owner_destruction_called); 90 | CHECK(with_custom_weak_reference_count == 0); 91 | } 92 | 93 | void on_owner_destruction() const 94 | { 95 | CHECK(with_custom_weak_reference_count == 0); //owner is still alive but its refcount is 0, you cannot ressurrect! 96 | on_owner_destruction_called = true; 97 | } 98 | 99 | mutable bool on_owner_destruction_called = false; 100 | }; 101 | 102 | inline custom_weak_reference * with_custom_weak_reference::make_weak_reference(intptr_t count) const 103 | { return new custom_weak_reference(count, const_cast(this)); } 104 | } 105 | 106 | TEST_SUITE("traits") { 107 | 108 | TEST_CASE( "Weak ref counted st type traits are correct" ) { 109 | 110 | SUBCASE( "Base" ) { 111 | 112 | CHECK( weak_ref_counted_st::single_threaded); 113 | CHECK( !std::is_default_constructible_v> ); 114 | CHECK( !std::is_copy_constructible_v> ); 115 | CHECK( !std::is_move_constructible_v> ); 116 | CHECK( !std::is_copy_assignable_v> ); 117 | CHECK( !std::is_move_assignable_v> ); 118 | CHECK( !std::is_swappable_v> ); 119 | CHECK( !std::is_destructible_v> ); 120 | } 121 | 122 | SUBCASE( "Derived" ) { 123 | CHECK( derived_counted::single_threaded); 124 | CHECK( !std::is_default_constructible_v ); 125 | CHECK( !std::is_copy_constructible_v ); 126 | CHECK( !std::is_move_constructible_v ); 127 | CHECK( !std::is_copy_assignable_v ); 128 | CHECK( !std::is_move_assignable_v ); 129 | CHECK( !std::is_swappable_v ); 130 | CHECK( !std::is_destructible_v ); 131 | } 132 | 133 | SUBCASE( "Derived WeakRef" ) { 134 | 135 | CHECK( derived_counted::weak_value_type::single_threaded); 136 | CHECK( !std::is_default_constructible_v ); 137 | CHECK( !std::is_copy_constructible_v ); 138 | CHECK( !std::is_move_constructible_v ); 139 | CHECK( !std::is_copy_assignable_v ); 140 | CHECK( !std::is_move_assignable_v ); 141 | CHECK( !std::is_swappable_v ); 142 | CHECK( !std::is_destructible_v ); 143 | } 144 | 145 | SUBCASE( "Wrapped" ) { 146 | CHECK( wrapped_counted::single_threaded); 147 | CHECK( !std::is_default_constructible_v ); 148 | CHECK( !std::is_copy_constructible_v ); 149 | CHECK( !std::is_move_constructible_v ); 150 | CHECK( !std::is_copy_assignable_v ); 151 | CHECK( !std::is_move_assignable_v ); 152 | CHECK( !std::is_destructible_v ); 153 | } 154 | 155 | SUBCASE( "Wrapped WeakRef" ) { 156 | CHECK( wrapped_counted::weak_value_type::single_threaded); 157 | CHECK( !std::is_default_constructible_v ); 158 | CHECK( !std::is_copy_constructible_v ); 159 | CHECK( !std::is_move_constructible_v ); 160 | CHECK( !std::is_copy_assignable_v ); 161 | CHECK( !std::is_move_assignable_v ); 162 | CHECK( !std::is_destructible_v ); 163 | } 164 | } 165 | 166 | } 167 | 168 | TEST_SUITE("ref_counted_st") { 169 | 170 | TEST_CASE( "Weak ref counted st works" ) { 171 | 172 | SUBCASE( "Derived" ) { 173 | auto original = refcnt_attach(new derived_counted()); 174 | auto weak1 = original->get_weak_ptr(); 175 | CHECK(derived_count == 1); 176 | auto strong1 = weak1->lock(); 177 | CHECK(original == strong1); 178 | auto weak2 = strong1->get_weak_ptr(); 179 | CHECK(weak1 == weak2); 180 | auto weak3 = weak_cast(strong1); 181 | CHECK(weak1 == weak3); 182 | original.reset(); 183 | strong1.reset(); 184 | CHECK(derived_count == 0); 185 | 186 | strong1 = weak1->lock(); 187 | CHECK(!strong1); 188 | } 189 | 190 | SUBCASE( "Const Derived" ) { 191 | refcnt_ptr original = refcnt_attach(new derived_counted()); 192 | auto weak1 = original->get_weak_ptr(); 193 | CHECK(derived_count == 1); 194 | auto strong1 = weak1->lock(); 195 | CHECK(original == strong1); 196 | auto weak2 = strong1->get_weak_ptr(); 197 | CHECK(weak1 == weak2); 198 | original.reset(); 199 | strong1.reset(); 200 | CHECK(derived_count == 0); 201 | 202 | strong1 = weak1->lock(); 203 | CHECK(!strong1); 204 | strong1 = strong_cast(weak2); 205 | CHECK(!strong1); 206 | } 207 | 208 | SUBCASE( "Wrapped" ) { 209 | 210 | auto p = refcnt_attach(new wrapped_counted()); 211 | CHECK(wrapped_count == 1); 212 | CHECK(p->value == 5); 213 | p.reset(); 214 | CHECK( wrapped_count == 0 ); 215 | } 216 | 217 | SUBCASE( "Custom Weak Reference" ) { 218 | 219 | auto strong = refcnt_attach(new with_custom_weak_reference); 220 | CHECK(with_custom_weak_reference_count == 1); 221 | auto weak = strong->get_weak_ptr(); 222 | auto strong1 = weak->lock(); 223 | CHECK( strong1 == strong ); 224 | CHECK(with_custom_weak_reference_count == 1); 225 | strong.reset(); 226 | CHECK(with_custom_weak_reference_count == 1); 227 | strong1.reset(); 228 | CHECK(with_custom_weak_reference_count == 0); 229 | strong1 = weak->lock(); 230 | CHECK(!strong1); 231 | } 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /tools/create-release: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S python3 -u 2 | 3 | # pylint: disable=missing-module-docstring, missing-function-docstring, line-too-long 4 | 5 | import sys 6 | import re 7 | import subprocess 8 | 9 | from pathlib import Path 10 | from datetime import date 11 | 12 | MYPATH = Path(__file__).parent 13 | ROOT = MYPATH.parent 14 | 15 | NEW_VER = sys.argv[1] 16 | 17 | unreleased_link_pattern = re.compile(r"^\[Unreleased\]: (.*)$", re.DOTALL) 18 | lines = [] 19 | with open(ROOT / "CHANGELOG.md", "rt", encoding='utf-8') as change_log: 20 | for line in change_log.readlines(): 21 | # Move Unreleased section to new version 22 | if re.fullmatch(r"^## Unreleased.*$", line, re.DOTALL): 23 | lines.append(line) 24 | lines.append("\n") 25 | lines.append( 26 | f"## [{NEW_VER}] - {date.today().isoformat()}\n" 27 | ) 28 | else: 29 | lines.append(line) 30 | lines.append(f'[{NEW_VER}]: https://github.com/gershnik/intrusive_shared_ptr/releases/v{NEW_VER}\n') 31 | 32 | with open(ROOT / "CHANGELOG.md", "wt", encoding='utf-8') as change_log: 33 | change_log.writelines(lines) 34 | 35 | (ROOT / "VERSION").write_text(f'{NEW_VER}\n') 36 | 37 | subprocess.run(['git', 'add', ROOT / "CHANGELOG.md", ROOT / "VERSION"], check=True) 38 | subprocess.run(['git', 'commit', '-m', f'chore: creating version {NEW_VER}'], check=True) 39 | subprocess.run(['git', 'tag', f'v{NEW_VER}'], check=True) 40 | -------------------------------------------------------------------------------- /tools/make-module: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env -S python3 -u 2 | 3 | # Copyright 2024 Eugene Gershnik 4 | # 5 | # Use of this source code is governed by a BSD-style 6 | # license that can be found in the LICENSE file or at 7 | # https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE 8 | # 9 | 10 | # pylint: disable=missing-module-docstring, missing-function-docstring, line-too-long 11 | 12 | import sys 13 | import re 14 | from pathlib import Path 15 | from typing import Dict, List 16 | 17 | 18 | comment_start_re = re.compile(r'\s*/\*.*') 19 | comment_end_re = re.compile(r'.*\*/\s*') 20 | sys_include_re = re.compile(r'\s*#\s*include\s+<([^>]+)>\s*') 21 | lib_include_re = re.compile(r'\s*#\s*include\s+]+)>\s*') 22 | user_include_re = re.compile(r'\s*#\s*include\s+"([^"]+)"\s*') 23 | 24 | 25 | SPECIAL_HEADERS = { 26 | 'version': '', 27 | 28 | 'format': 29 | ''' 30 | #if __has_include() 31 | #include 32 | #endif 33 | '''.strip(), 34 | 35 | 'CoreFoundation/CoreFoundation.h': 36 | ''' 37 | #if (defined(__APPLE__) && defined(__MACH__)) 38 | ##INCLUDE## 39 | #endif 40 | '''.lstrip(), 41 | 42 | 'Unknwn.h': 43 | ''' 44 | #if defined(_WIN32) 45 | #define NOMINMAX 46 | ##INCLUDE## 47 | #endif 48 | '''.lstrip(), 49 | 50 | 'Python.h': 51 | ''' 52 | #if __has_include() 53 | ##INCLUDE## 54 | #endif 55 | '''.lstrip() 56 | } 57 | 58 | 59 | def process_header(folder: Path, path: Path, sys_includes: List[str], processed_headers:Dict[str, bool], strip_initial_comment: bool): 60 | 61 | ret = "" 62 | initial_comment_state = 0 63 | with open(path, "rt", encoding='utf-8') as headerfile: 64 | for line in headerfile: 65 | if strip_initial_comment: 66 | if initial_comment_state == 0: 67 | m = comment_start_re.match(line) 68 | if m: 69 | initial_comment_state = 1 70 | continue 71 | elif initial_comment_state == 1: 72 | m = comment_end_re.match(line) 73 | if m: 74 | initial_comment_state = 2 75 | continue 76 | 77 | m = user_include_re.match(line) 78 | if not m: 79 | m = lib_include_re.match(line) 80 | if m: 81 | name = m.group(1) 82 | if not processed_headers.get(name): 83 | new_path = (folder / name).absolute() 84 | ret += process_header(new_path.parent, new_path, sys_includes, processed_headers, strip_initial_comment=True) 85 | processed_headers[name] = True 86 | continue 87 | 88 | m = sys_include_re.match(line) 89 | if m: 90 | sys_includes.append(m.group(1)) 91 | continue 92 | 93 | ret += line 94 | 95 | return ret 96 | 97 | 98 | def combine_headers(folder: Path, template: Path, output: Path): 99 | sys_includes = [] 100 | processed_headers = {} 101 | 102 | text = process_header(folder, template, sys_includes, processed_headers, strip_initial_comment=False) 103 | 104 | sys_includes = list(set(sys_includes)) 105 | sys_includes.sort() 106 | sys_includes_text = "" 107 | for sys_include in sys_includes: 108 | inc_line = f"#include <{sys_include}>" 109 | if (repl := SPECIAL_HEADERS.get(sys_include)) is not None: 110 | repl = repl.replace('##INCLUDE##', inc_line) 111 | else: 112 | repl = inc_line 113 | sys_includes_text += ("\n" + repl) 114 | 115 | text = text.replace("##SYS_INCLUDES##", sys_includes_text) 116 | 117 | output.parent.mkdir(parents=True, exist_ok=True) 118 | with open(output, "wt", encoding='utf-8') as outfile: 119 | print(text, file=outfile) 120 | 121 | def main(): 122 | mydir = Path(__file__).parent 123 | incdir = mydir.parent / 'inc/intrusive_shared_ptr' 124 | moddir = mydir.parent / 'modules' 125 | 126 | combine_headers(incdir, mydir / 'module-template.txt', moddir / 'isptr.cppm') 127 | 128 | if __name__ == "__main__": 129 | sys.exit(main()) 130 | -------------------------------------------------------------------------------- /tools/module-template.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 Eugene Gershnik 3 | 4 | Use of this source code is governed by a BSD-style 5 | license that can be found in the LICENSE file or at 6 | https://github.com/gershnik/intrusive_shared_ptr/blob/master/LICENSE.txt 7 | */ 8 | module; 9 | 10 | ##SYS_INCLUDES## 11 | 12 | export module isptr; 13 | 14 | #define ISPTR_EXPORTED export 15 | 16 | #include "intrusive_shared_ptr.h" 17 | #include "ref_counted.h" 18 | #include "apple_cf_ptr.h" 19 | #include "com_ptr.h" 20 | #if __has_include() 21 | #include "python_ptr.h" 22 | #endif 23 | #include "refcnt_ptr.h" 24 | --------------------------------------------------------------------------------