Add 'xo-refcnt/' from commit 'bcd86e5324'

git-subtree-dir: xo-refcnt
git-subtree-mainline: ce2fc80bfd
git-subtree-split: bcd86e5324
This commit is contained in:
Roland Conybeare 2025-05-10 18:31:14 -05:00
commit 2075b654af
17 changed files with 1278 additions and 0 deletions

View file

@ -0,0 +1,80 @@
# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage.
# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml
name: CMake on a single platform
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install catch2
# install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]]
run: sudo apt-get install -y catch2
# ----------------------------------------------------------------
- name: Clone xo-cmake
uses: actions/checkout@v3
with:
repository: Rconybea/xo-cmake
path: repo/xo-cmake
- name: Configure xo-cmake
run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake
- name: Build xo-cmake (trivial)
run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}}
- name: Install xo-cmake
run: cmake --install ${{github.workspace}}/build_xo-cmake
# ----------------------------------------------------------------
- name: Clone indentlog
uses: actions/checkout@v3
with:
repository: Rconybea/indentlog
path: repo/indentlog
- name: Configure indentlog
# configure cmake for indentlog in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog
- name: Build indentlog
run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}}
- name: Install indentlog
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_indentlog
# ----------------------------------------------------------------
- name: Configure self (refcnt)
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build refcnt
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}}
- name: Test refcnt
working-directory: ${{github.workspace}}/build_refcnt
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C ${{env.BUILD_TYPE}}

9
xo-refcnt/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
# emacs workspace config
.projectile
# symlink to ${mybuilddirectory}/compile_commands.json for LSP
compile_commands.json
# LSP keeps state here
.cache
# typical build directories
.build*
ccov

31
xo-refcnt/CMakeLists.txt Normal file
View file

@ -0,0 +1,31 @@
# refcnt/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(refcnt VERSION 0.1)
include(GNUInstallDirs)
include(cmake/xo-bootstrap-macros.cmake)
xo_cxx_toplevel_options3()
# ----------------------------------------------------------------
# c++ settings
set(XO_PROJECT_NAME refcnt) # is this used?
set(PROJECT_CXX_FLAGS "")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only!
add_definitions(${PROJECT_CXX_FLAGS})
# ----------------------------------------------------------------
add_subdirectory(src)
add_subdirectory(utest)
# ----------------------------------------------------------------
# cmake export
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# end CMakeLists.txt

104
xo-refcnt/README.md Normal file
View file

@ -0,0 +1,104 @@
# intrusive reference counting
Refcnt is a small shared library supplying intrusive reference counting.
## Features
- base class `ref::Refcounted`.
Application classes opt-in to reference counting by inheriting this class.
- common base simplifies connecting to common-base-object applications such as python, java etc.
## Getting Started
### build + install `xo-cmake` dependency (cmake macros)
see [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake)
Installs a few cmake ingredients, along with a build assistant for XO projects such as this one.
### build + install XO deps
```
$ xo-build --clone --configure --build --install xo-indentlog
```
### copy `refcnt` repository locally
```
$ xo-build --clone xo-refcnt
```
or equivalently
```
$ git clone git@github.com:Rconybea/refcnt.git xo-refcnt
```
### build + install
```
$ xo-build --configure --build --install xo-refcnt
```
or equivalently:
```
$ mkdir xo-refcnt/.build
$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-refcnt -B xo-refcnt/.build
$ cmake --build xo-refcnt/.build
$ cmake --install xo-refcnt/.build
```
`CMAKE_PREFIX_PATH` should point to the prefix where `xo-indentlog` is installed
alternatively, if you're a nix user:
```
$ git clone git@github.com:rconybea/xo-nix.git
$ ls -d xo-nix
xo-nix
$ cd xo-nix
$ nix-build -A xo-refcnt
```
### build for unit test coverage
```
$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_PREFIX_PATH=${PREFIX} -S xo-refcnt -B xo-refcnt/.build-ccov
$ cmake --build xo-refcnt/.build-ccov
```
### LSP support
```
$ cd xo-refcnt
$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree
```
## Examples
### 1
```
#include "xo/refcnt/Refcounted.hpp"
using xo::ref::Refcounted;
struct MyObject : public Refcounted {
static rp<MyObject> make() { return new MyObject(); }
private:
// intrusively-reference-counted objects should only be heap-allocated
MyObject() { ... }
};
int main() {
// create reference-counted instance
auto x = MyObject::make();
auto y = x;
// x,y refer to the same instance.
x = nullptr;
// y holds last reference
y = nullptr;
// MyObject has been deleted
}
```
### 2
To log reference-counting activity
```
xo::ref::intrusive_ptr_set_debug(true);
```

View file

@ -0,0 +1,6 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(indentlog)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

View file

@ -0,0 +1,35 @@
# ----------------------------------------------------------------
# for example:
# $ PREFIX=/usr/local # for example
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
#
# will get
# CMAKE_MODULE_PATH
# from xo-cmake-config --cmake-module-path
#
# and expect .cmake macros in
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
# ----------------------------------------------------------------
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND")
message(FATAL "could not find xo-cmake-config executable")
endif()
message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}")
if (NOT XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# default to typical install location for xo-project-macros
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo_cxx)
xo_cxx_bootstrap_message()

View file

@ -0,0 +1,92 @@
/* @file demangle.hpp */
#pragma once
#include <string>
#include <string_view>
#include <array> // std::array
#include <utility> // std::index_sequence
namespace xo {
namespace reflect {
template <std::size_t...Idxs>
constexpr auto
substring_as_array(std::string_view str,
std::index_sequence<Idxs...> indexes)
{
//return std::array<char, indexes.size()+1>{str[Idxs]..., '\n'};
return std::array<char, indexes.size()>{str[Idxs]...};
} /*substring_as_array*/
template <typename T> constexpr auto type_name_array() {
#if defined(__clang__)
constexpr auto prefix = std::string_view{"[T = "};
constexpr auto suffix = std::string_view{"]"};
constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(__GNUC__)
constexpr auto prefix = std::string_view{"with T = "};
constexpr auto suffix = std::string_view{"]"};
constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(_MSC_VER)
constexpr auto prefix = std::string_view{"type_name_array<"};
constexpr auto suffix = std::string_view{">(void)"};
constexpr auto function = std::string_view{__FUNCSIG__};
#else
# error type_name_array: Unsupported compiler
#endif
constexpr auto start = function.find(prefix) + prefix.size();
constexpr auto end = function.rfind(suffix);
//static_assert(start < end);
constexpr auto name = function.substr(start, (end - start));
constexpr auto ixseq = std::make_index_sequence<name.size()>{};
return substring_as_array(name, ixseq);
} /*type_name_array*/
template <typename T>
struct type_name_holder {
static inline constexpr auto value = type_name_array<T>();
};
template <typename T>
constexpr auto type_name() -> std::string_view
{
constexpr auto& value = type_name_holder<T>::value;
return std::string_view{value.data(), value.size()};
}
#ifdef NOT_IN_USE
template <std::string_view const&... Strs>
struct join
{
// Join all strings into a single std::array of chars
static constexpr auto impl() noexcept
{
constexpr std::size_t len = (Strs.size() + ... + 0);
std::array<char, len + 1> arr{};
auto append = [i = 0, &arr](auto const& s) mutable {
for (auto c : s) arr[i++] = c;
};
(append(Strs), ...);
arr[len] = 0;
return arr;
}
// Give the joined string static storage
static constexpr auto arr = impl();
// View as a std::string_view
static constexpr std::string_view value {arr.data(), arr.size() - 1};
};
// Helper to get the value out
template <std::string_view const&... Strs>
static constexpr auto join_v = join<Strs...>::value;
#endif
} /*namespace reflect*/
} /*namespace xo*/
/* end demangle.hpp */

View file

@ -0,0 +1,29 @@
/* @file Displayable.hpp */
#pragma once
#include "Refcounted.hpp"
namespace xo {
namespace ref {
class Displayable : public Refcount {
public:
/* write some kind of human-readable representation on stream */
virtual void display(std::ostream & os) const = 0;
std::string display_string() const;
}; /*Displayable*/
/* see also
* operator<<(std::ostream &, intrusive_ptr<T> const &)
* in [Refcounted.hpp]
*/
inline std::ostream &
operator<<(std::ostream &os, Displayable const & x) {
x.display(os);
return os;
} /*operator<<*/
} /*namespace ref*/
} /*namespace xo*/
/* end Displayable.hpp */

View file

@ -0,0 +1,351 @@
/* @file Refcounted.hpp */
#pragma once
#include "xo/indentlog/scope.hpp"
#include "xo/cxxutil/demangle.hpp"
//#include <boost/intrusive_ptr.hpp>
#include <atomic>
#include <cassert>
namespace xo {
namespace ref {
template <typename T>
class intrusive_ptr;
}
template <typename T>
using rp = ref::intrusive_ptr<T>;
namespace ref {
class Refcount;
template<typename T>
class Borrow;
/* originally used boost::instrusive_ptr<>.
* ran into a bug. probably mine, but implemented
* refcounting inline for debugging
*/
template<typename T>
class intrusive_ptr {
public:
using element_type = T;
public:
intrusive_ptr() : ptr_(nullptr) {}
intrusive_ptr(T * x) : ptr_(x) {
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
intrusive_ptr_log_ctor(sc_self_type, this, x);
#endif
intrusive_ptr_add_ref(ptr_);
} /*ctor*/
/* NOTE: need exactly this form for copy-constructor
* clang11 will not recognize template form below as
* supplying copy ctor, and default version is broken for
* instrusive_ptr.
*/
intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) {
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
intrusive_ptr_log_cctor(sc_self_type, this, x.get());
#endif
intrusive_ptr_add_ref(ptr_);
} /*cctor*/
/* create from instrusive pointer to some related type S */
template<typename S>
intrusive_ptr(intrusive_ptr<S> const & x) : ptr_(x.get()) {
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
intrusive_ptr_log_cctor(sc_self_type, this, x.get());
#endif
intrusive_ptr_add_ref(ptr_);
} /*cctor*/
/* move ctor -- in this case don't need to update refcount */
intrusive_ptr(intrusive_ptr && x) : ptr_{std::move(x.ptr_)} {
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
intrusive_ptr_log_mctor(sc_self_type, this, ptr_);
#endif
/* since we're moving from x, need to make sure x dtor
* doesn't decrement refcount
*/
x.ptr_ = nullptr;
}
/* aliasing ctor. see ctor (8) here:
* [[https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr]]
* and this dicsussion:
* [[https://stackoverflow.com/questions/49178231/pybind11-multiple-inheritance-with-custom-holder-type-fails-to-cast-to-base-type/73131206#73131206]]
*/
template<typename Y>
intrusive_ptr(intrusive_ptr<Y> const & /*x*/, element_type * y) : ptr_{y} {
if (std::is_same<Y, element_type>()) {
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
intrusive_ptr_log_actor(sc_self_type, this, y);
#endif
intrusive_ptr_add_ref(ptr_);
; /* trivial aliasing, proceed */
} else {
using xo::xtag;
throw std::runtime_error(tostr("attempt to use aliasing ctor with",
xtag("Y", reflect::type_name<Y>()),
xtag("T", reflect::type_name<T>())));
}
} /*ctor*/
~intrusive_ptr() {
T * x = this->ptr_;
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
intrusive_ptr_log_dtor(sc_self_type, this, x);
#endif
this->ptr_ = nullptr;
intrusive_ptr_release(x);
} /*dtor*/
static bool compare(intrusive_ptr<T> const & x,
intrusive_ptr<T> const & y) {
return ptrdiff_t(x.get() - y.get());
}
Borrow<T> borrow() const;
T * get() const { return ptr_; }
T * operator->() const { return ptr_; }
operator bool() const { return ptr_ != nullptr; }
intrusive_ptr<T> & operator=(intrusive_ptr<T> const & rhs) {
T * x = rhs.get();
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
intrusive_ptr_log_assign(sc_self_type, this, x);
#endif
T * old = this->ptr_;
this->ptr_ = x;
intrusive_ptr_add_ref(x);
intrusive_ptr_release(old);
return *this;
} /*operator=*/
intrusive_ptr<T> & operator=(intrusive_ptr<T> && rhs) {
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
intrusive_ptr_log_massign(sc_self_type, this, rhs.get());
#endif
std::swap(this->ptr_, rhs.ptr_);
/* dtor on rhs will decrement refcount on old value of this->ptr_
* don't increment for new value, since refcount just transfers from rhs to *this
*/
return *this;
} /*operator=*/
private:
static constexpr std::string_view sc_self_type = xo::reflect::type_name<intrusive_ptr<T>>();
private:
T * ptr_ = nullptr;
}; /*intrusive_ptr*/
template<typename T>
inline bool operator==(intrusive_ptr<T> const & x,
intrusive_ptr<T> const & y) { return intrusive_ptr<T>::compare(x, y) == 0; }
class Refcount {
public:
Refcount() : reference_counter_(0) {}
/* WARNING: virtual dtor here is essential,
* since it's what allows us to invoke delete on a Refcount*,
* for an object of some derived class type T. Otherwise clang
* will use different addresses for Refcount-part and T-part of
* such instance, which means pointer given to delete will not be
* the same as pointer returned from new
*/
virtual ~Refcount() = default;
uint32_t reference_counter() const { return reference_counter_.load(); }
private:
friend uint32_t intrusive_ptr_refcount(Refcount *);
friend void intrusive_ptr_add_ref(Refcount *);
friend void intrusive_ptr_release(Refcount *);
private:
std::atomic<uint32_t> reference_counter_;
}; /*Refcount*/
inline uint32_t
intrusive_ptr_refcount(Refcount * x) {
/* reporting accurately for diagnostics */
if (x)
return x->reference_counter_.load();
else
return 0;
} /*intrusive_ptr_refcount*/
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
extern void intrusive_ptr_set_debug(bool x);
extern void intrusive_ptr_log_ctor(std::string_view const & self_type,
void * this_ptr,
Refcount * x);
/* here actor short for 'aliasing ctor' */
extern void intrusive_ptr_log_actor(std::string_view const & self_type,
void * this_ptr,
Refcount * x);
extern void intrusive_ptr_log_cctor(std::string_view const & self_type,
void * this_ptr,
Refcount * x);
extern void intrusive_ptr_log_mctor(std::string_view const & self_type,
void *this_ptr,
Refcount * x);
extern void intrusive_ptr_log_dtor(std::string_view const & self_type,
void * this_ptr,
Refcount * x);
extern void intrusive_ptr_log_assign(std::string_view const & self_type,
void * this_ptr,
Refcount * x);
extern void intrusive_ptr_log_massign(std::string_view const & self_type,
void *this_ptr,
Refcount * x);
#endif
extern void intrusive_ptr_add_ref(Refcount * x);
extern void intrusive_ptr_release(Refcount * x);
template<typename T>
inline std::ostream &
operator<<(std::ostream & os, intrusive_ptr<T> const & x) {
if(x.get()) {
os << *(x.get());
} else {
os << "<nullptr " << reflect::type_name<T>() << ">";
}
return os;
} /*operator<<*/
/** Wrap a (presumably non-reference-counted) class T so that it has a refcount.
**/
template <typename T>
class RefcountWrapper : public Refcount, public T {
public:
template<typename... Args>
RefcountWrapper(Args... args) : Refcount(), T(std::forward<Args...>(args...)) {}
};
/* borrow a reference-counted pointer to pass down the stack
* 1. borrowed pointer intended to replace:
* a. code like
* foo(rp<T> x),
* passing rp<T> by value requires increment/decrement pair,
* which is superfluous given that caller holds reference throughout
* b. code like
* foo(rp<T> const & x)
* passing rp<T> by reference requires double-indirection in called
* code
* 2. borrowed pointer does not check/maintain reference count.
* it should never be stored in a struct; intended strictly
* to be passed down stack
* 3. just the same, want to be able to copy the borrowed pointer,
* to avoid double-indirection
* 4. also can promote borrowed pointer to full reference-counted
* whenever desired
*/
template<typename T>
class Borrow {
public:
Borrow() = default;
template <typename S>
Borrow(rp<S> const & x) : ptr_(x.get()) {}
template <typename S>
Borrow(S * x) : ptr_(x) {}
Borrow(Borrow const & x) = default;
/* convert from another borrow, if it has compatible pointer type */
template<typename S>
Borrow(Borrow<S> const & x) : ptr_(x.get()) {}
/* dynamic cast from a pointer to an object of some convertible type */
template<typename S>
static Borrow<T> from(Borrow<S> x) {
return Borrow(dynamic_cast<T *>(x.get()));
} /*from*/
/* promote from native pointer */
static Borrow<T> from_native(T * x) {
return Borrow(x);
} /*from_native*/
T * get() const { return ptr_; }
rp<T> promote() const { return rp<T>(ptr_); }
T & operator*() const { return *ptr_; }
T * operator->() const { return ptr_; }
operator bool() const { return ptr_ != nullptr; }
static int32_t compare(Borrow const & x, Borrow const & y) {
return ptrdiff_t(x.get() - y.get());
} /*compare*/
static int32_t compare(rp<T> const & x, Borrow const & y) {
return ptrdiff_t(x.get() - y.get());
} /*compare*/
template <typename S>
Borrow<T> & operator=(const Borrow<S> & x) {
ptr_ = x.get();
return *this;
}
Borrow& operator=(const Borrow& x) {
ptr_ = x.get();
return *this;
}
private:
T * ptr_ = nullptr;
}; /*Borrow*/
template<typename T>
inline bool operator==(Borrow<T> x, Borrow<T> y) { return Borrow<T>::compare(x, y) == 0; }
template<typename T>
inline bool operator==(rp<T> const & x, Borrow<T> y) { return Borrow<T>::compare(x, y) == 0; }
template<typename T>
using brw = Borrow<T>;
template<typename T>
Borrow<T>
intrusive_ptr<T>::borrow() const {
return Borrow<T>(*this);
} /*borrow*/
template<typename T>
inline std::ostream &
operator<<(std::ostream & os, Borrow<T> x) {
if (x) {
os << *x;
} else {
os << "<nullptr " << reflect::type_name<T>() << ">";
}
return os;
} /*operator<<*/
} /*namespace ref*/
} /*namespace xo*/
/* end Refcounted.hpp */

View file

@ -0,0 +1,28 @@
/* @file Unowned.hpp */
namespace xo {
namespace ref {
/* use this is a holder type for pointers that pybind11 should treat
* as "not-my-problem". in particular that pybind11 should never delete.
*/
template<typename T>
class unowned_ptr {
public:
unowned_ptr(T * x) : ptr_{x} {}
unowned_ptr(unowned_ptr const & x) = default;
~unowned_ptr() = default;
T * get() const { return ptr_; }
T * operator->() const { return ptr_; }
operator bool() const { return ptr_ != nullptr; }
unowned_ptr<T> & operator=(unowned_ptr<T> const & rhs) = default;
private:
T * ptr_ = nullptr;
}; /*unowned_ptr*/
} /*namespace ref*/
} /*namespace xo*/
/* end Unowned.hpp */

View file

@ -0,0 +1,10 @@
set(SELF_LIB refcnt)
set(SELF_SRCS Refcounted.cpp Displayable.cpp)
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
xo_install_include_tree3(include/xo/cxxutil)
# NOTE:
# dependency set here must be kept consistent with refcnt/cmake/refcntConfig.cmake.in
#
xo_dependency(${SELF_LIB} indentlog)

View file

@ -0,0 +1,16 @@
/* @file Displayable.cpp */
#include "Displayable.hpp"
namespace xo {
using xo::tostr;
namespace ref {
std::string
Displayable::display_string() const {
return tostr(*this);
} /*display_string*/
} /*namespace ref*/
} /*namespace xo*/
/* end Displayable.cpp */

View file

@ -0,0 +1,159 @@
/* @file Refcounted.cpp */
#include "Refcounted.hpp"
namespace xo {
namespace ref {
#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING
namespace {
/* verbose logging for intrusive_ptr */
static bool s_logging_enabled = false;
void
intrusive_ptr_log_aux(std::string_view const & self_type,
std::string_view const & method_name,
void * this_ptr,
Refcount * x)
{
scope lscope(XO_LITERAL(log_level::verbose, self_type, method_name),
"enter",
xtag("this", this_ptr),
xtag("x", x),
xtag("n", intrusive_ptr_refcount(x)));
} /*intrusive_ptr_log_aux*/
} /*namespace*/
void
intrusive_ptr_set_debug(bool debug_flag) {
s_logging_enabled = debug_flag;
} /*intrusive_ptr_set_debug*/
void
intrusive_ptr_log_ctor(std::string_view const & self_type,
void * this_ptr,
Refcount * x)
{
if (s_logging_enabled)
intrusive_ptr_log_aux(self_type, "::ctor", this_ptr, x);
} /*intrusive_ptr_log_ctor*/
void
intrusive_ptr_log_actor(std::string_view const & self_type,
void * this_ptr,
Refcount * x)
{
if (s_logging_enabled)
intrusive_ptr_log_aux(self_type, "::actor", this_ptr, x);
} /*intrusive_ptr_log_actor*/
void
intrusive_ptr_log_cctor(std::string_view const & self_type,
void * this_ptr,
Refcount * x)
{
if (s_logging_enabled)
intrusive_ptr_log_aux(self_type, "::cctor", this_ptr, x);
} /*intrusive_ptr_log_cctor*/
void
intrusive_ptr_log_mctor(std::string_view const & self_type,
void * this_ptr,
Refcount * x)
{
if (s_logging_enabled)
intrusive_ptr_log_aux(self_type, "::mctor", this_ptr, x);
} /*intrusive_ptr_log_mctor*/
void
intrusive_ptr_log_dtor(std::string_view const & self_type,
void * this_ptr,
Refcount * x)
{
if (s_logging_enabled)
intrusive_ptr_log_aux(self_type, "::dtor", this_ptr, x);
} /*intrusive_ptr_log_dtor*/
void
intrusive_ptr_log_assign(std::string_view const & self_type,
void * this_ptr,
Refcount * x)
{
if (s_logging_enabled)
intrusive_ptr_log_aux(self_type, "::=", this_ptr, x);
} /*intrusive_ptr_log_assign*/
void
intrusive_ptr_log_massign(std::string_view const & self_type,
void * this_ptr,
Refcount * x)
{
if (s_logging_enabled)
intrusive_ptr_log_aux(self_type, "::m=", this_ptr, x);
} /*intrusive_ptr_log_massign*/
#endif
void
intrusive_ptr_add_ref(Refcount * x)
{
/* for adding reference -- can use relaxed order,
* since any reordering of a set of intrusive_ptr_add_ref()
* calls is ok, provided no intervening intrusive_ptr_release()
* calls.
*/
bool success = (x == nullptr);
while(!success) {
uint32_t n = x->reference_counter_.load(std::memory_order_relaxed);
success = x->reference_counter_.compare_exchange_strong(n, n+1,
std::memory_order_relaxed);
}
} /*intrusive_ptr_add_ref*/
void
intrusive_ptr_release(Refcount * x)
{
using xo::scope;
using xo::xtag;
scope log(XO_ENTER0(verbose),
"enter",
xtag("x", x),
xtag("n", x ? x->reference_counter_.load() : 0));
/* for decrement, need acq_rel ordering */
bool success = (x == nullptr);
uint32_t n = 0;
while(!success) {
n = x->reference_counter_.load(std::memory_order_acquire);
if(n == static_cast<uint32_t>(-1)) {
log && log("detected double-delete attempt",
xtag("x", x),
xtag("n", n));
assert(false);
}
success = x->reference_counter_.compare_exchange_strong(n, n-1,
std::memory_order_acq_rel);
}
if(n == 1) {
/* just deleted the last reference, so recover the object */
log && log("delete object with 0 refs");
/* for good measure: replace refcount with -1,
* in hope of detecting a double-delete attempt
*/
x->reference_counter_.store(static_cast<uint32_t>(-1));
delete x;
}
} /*intrusive_ptr_release*/
} /*namespace ref*/
} /*namespace xo*/
/* end Refcounted.cpp */

View file

@ -0,0 +1,15 @@
# build unittest 'refcnt/utest/utest.refcnt
set(SELF_EXECUTABLE_NAME utest.refcnt)
# These tests can use the Catch2-provided main
set(SELF_SOURCE_FILES intrusive_ptr.test.cpp refcnt_utest_main.cpp)
xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES})
# ----------------------------------------------------------------
# 3rd party dependency: catch2:
xo_self_dependency(${SELF_EXECUTABLE_NAME} refcnt)
xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2)
# end CMakeLists.txt

7
xo-refcnt/utest/README Normal file
View file

@ -0,0 +1,7 @@
* to run unit tests for this directoyr
$ cd path/to/kalman/build
$ ./refcnt/utest/utest.refcnt

View file

@ -0,0 +1,300 @@
/* @file intrusive_ptr.test.cpp */
#include "Refcounted.hpp"
#include "xo/indentlog/scope.hpp"
#include <catch2/catch.hpp>
#include <string_view>
#include <type_traits>
namespace xo {
using xo::ref::Refcount;
using xo::ref::Borrow;
using xo::ref::brw;
using xo::ref::intrusive_ptr_refcount;
using xo::ref::intrusive_ptr_add_ref;
using xo::ref::intrusive_ptr_release;
namespace ut {
namespace {
static uint32_t ctor_count = 0;
static uint32_t dtor_count = 0;
/* empty object, except for refcount */
class JustRefcount : public ref::Refcount {
public:
JustRefcount() { ++ctor_count; }
~JustRefcount() { ++dtor_count; }
}; /*JustRefcount*/
inline std::ostream & operator<<(std::ostream & os, JustRefcount & x) {
os << "JustRefcount";
return os;
} /*operator<<*/
} /*namespace*/
TEST_CASE("refcount", "[refcnt][trivial]") {
REQUIRE(std::is_default_constructible<ref::Refcount>() == true);
REQUIRE(std::has_virtual_destructor<ref::Refcount>() == true);
/* refcount object self-initializes to 0 */
Refcount x;
REQUIRE(x.reference_counter() == 0);
} /*TEST_CASE(refcount)*/
TEST_CASE("null-intrusive-ptr", "[refcnt][trivial]") {
//constexpr std::string_view c_self = "TEST_CASE:null-intrusive-ptr";
REQUIRE(std::has_virtual_destructor<JustRefcount>() == true);
rp<JustRefcount> p1;
rp<JustRefcount> p2;
REQUIRE(sizeof(p1) == sizeof(JustRefcount*));
REQUIRE(p1.get() == nullptr);
REQUIRE(p1.operator->() == nullptr);
REQUIRE(p2.get() == nullptr);
REQUIRE(p2.operator->() == nullptr);
/* can assign a nullptr */
rp<JustRefcount> p3;
REQUIRE(p3.get() == nullptr);
p3 = p1;
REQUIRE(p3.get() == nullptr);
/* can use aux functions on null pointers */
REQUIRE(intrusive_ptr_refcount(p1.get()) == 0);
intrusive_ptr_add_ref(nullptr);
intrusive_ptr_release(nullptr);
/* can borrow a null intrusive_ptr */
brw<JustRefcount> p1_brw = p1.borrow();
brw<JustRefcount> p2_brw = p2.borrow();
REQUIRE(p1_brw.get() == nullptr);
REQUIRE(p1_brw.operator->() == nullptr);
/* null borrow is false-y */
REQUIRE(p1_brw == false);
/* can promote a borrowed pointer */
rp<JustRefcount> pp = p1_brw.promote();
REQUIRE(p1.get() == pp.get());
/* comparisons */
REQUIRE(Borrow<JustRefcount>::compare(p1_brw, p2_brw) == 0);
REQUIRE(p1_brw == p2_brw);
REQUIRE((p1_brw != p2_brw) == false);
REQUIRE(p1 == p1_brw);
REQUIRE((p1 != p1_brw) == false);
REQUIRE(p1_brw == p1);
REQUIRE((p1_brw != p1) == false);
} /*TEST_CASE(null-intrusive_ptr)*/
TEST_CASE("intrusive-ptr-identity", "[refcnt][identity]")
{
uint32_t cc = ctor_count;
uint32_t dc = dtor_count;
rp<JustRefcount> p1(new JustRefcount());
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
REQUIRE(p1.get() != nullptr);
REQUIRE(p1.get() == p1.operator->());
REQUIRE(intrusive_ptr_refcount(p1.get()) == 1);
REQUIRE(p1->reference_counter() == 1);
intrusive_ptr_add_ref(p1.get());
REQUIRE(intrusive_ptr_refcount(p1.get()) == 2);
intrusive_ptr_release(p1.get());
REQUIRE(intrusive_ptr_refcount(p1.get()) == 1);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
rp<JustRefcount> p2(new JustRefcount());
REQUIRE(ctor_count == cc + 2);
REQUIRE(dtor_count == dc);
REQUIRE(p2.get() != nullptr);
REQUIRE(p2.get() != p1.get());
REQUIRE(p2.get() == p2.operator->());
REQUIRE(p2->reference_counter() == 1);
/* can borrow a non-null intrusive-ptr */
brw<JustRefcount> p1_brw = p1.borrow();
REQUIRE(p1_brw.get() == p1.get());
/* borrowing does not change refcount, borrow not tracked */
REQUIRE(ctor_count == cc + 2);
REQUIRE(dtor_count == dc);
REQUIRE(p1.get()->reference_counter() == 1);
/* copying borrowed pointer does not touch refcount */
brw<JustRefcount> p1_brw2 = p1_brw;
REQUIRE(ctor_count == cc + 2);
REQUIRE(dtor_count == dc);
REQUIRE(p1_brw2.get() == p1.get());
REQUIRE(p1.get()->reference_counter() == 1);
} /*TEST_CASE(identity-intrusive-ptr)*/
TEST_CASE("intrusive-ptr-release", "[refcnt][release]")
{
uint32_t cc = ctor_count;
uint32_t dc = dtor_count;
rp<JustRefcount> p1(new JustRefcount());
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
REQUIRE(p1.get() != nullptr);
REQUIRE(p1->reference_counter() == 1);
/* reference count going to 0 -> delete object */
p1 = nullptr;
REQUIRE(p1.get() == nullptr);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc + 1);
} /*TEST_CASE(intrusive-ptr-release)*/
TEST_CASE("intrusive-ptr-copy", "[refcnt][copy]")
{
uint32_t cc = ctor_count;
uint32_t dc = dtor_count;
rp<JustRefcount> p1(new JustRefcount());
JustRefcount * p1_native = p1.get();
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
REQUIRE(p1.get() != nullptr);
REQUIRE(p1->reference_counter() == 1);
/* copy ctor ran to make copy of p1, did not allocate */
rp<JustRefcount> p2(p1);
REQUIRE(p1->reference_counter() == 2);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
} /*TEST_CASE(intrusive-ptr-copy)*/
TEST_CASE("intrusive-ptr-move", "[refcnt][move]")
{
uint32_t cc = ctor_count;
uint32_t dc = dtor_count;
rp<JustRefcount> p1(new JustRefcount());
JustRefcount * p1_native = p1.get();
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
REQUIRE(p1.get() != nullptr);
REQUIRE(p1->reference_counter() == 1);
rp<JustRefcount> p2{std::move(p1)};
REQUIRE(p2->reference_counter() == 1);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
p2 = nullptr;
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc + 1);
} /*TEST_CASE(intrusive-ptr-move)*/
TEST_CASE("instrusive-ptr-assign", "[refcnt][assign]")
{
uint32_t cc = ctor_count;
uint32_t dc = dtor_count;
rp<JustRefcount> p1(new JustRefcount());
JustRefcount * p1_native = p1.get();
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
REQUIRE(p1.get() != nullptr);
REQUIRE(p1->reference_counter() == 1);
rp<JustRefcount> p2;
REQUIRE(p2.get() == nullptr);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
p2 = p1;
REQUIRE(p2.get() == p1.get());
REQUIRE(p2->reference_counter() == 2);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
p1 = nullptr;
REQUIRE(p2->reference_counter() == 1);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
p2 = nullptr;
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc + 1);
} /*TEST_CASE(intrusive-ptr-assign)*/
TEST_CASE("intrusive-ptr-move-assign", "[refcnt][move-assign]")
{
uint32_t cc = ctor_count;
uint32_t dc = dtor_count;
rp<JustRefcount> p1(new JustRefcount());
JustRefcount * p1_native = p1.get();
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
REQUIRE(p1.get() != nullptr);
REQUIRE(p1->reference_counter() == 1);
rp<JustRefcount> p2;
REQUIRE(p2.get() == nullptr);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
p2 = std::move(p1);
REQUIRE(p1.get() == nullptr);
REQUIRE(p2.get() == p1_native);
REQUIRE(p2->reference_counter() == 1);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
p1 = nullptr; /*no-op*/
REQUIRE(p2->reference_counter() == 1);
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc);
p2 = nullptr;
REQUIRE(ctor_count == cc + 1);
REQUIRE(dtor_count == dc + 1);
} /*TEST_CASE(intrusive-ptr-move-assign)*/
} /*namespace ut*/
} /*namespace xo*/
/* end intrusive_ptr.test.cpp */

View file

@ -0,0 +1,6 @@
/* @file refcnt_utest_main.cpp */
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
/* end refcnt_utest_main.cpp */