From 7f769ebda3d2bba9ab767e56adf2bc85a1d0c48a Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 26 Nov 2025 21:04:24 -0800 Subject: [PATCH] feat: small_unique_ptr --- test/unit/small_unique_ptr.cpp | 425 +++++++++++++++++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 test/unit/small_unique_ptr.cpp diff --git a/test/unit/small_unique_ptr.cpp b/test/unit/small_unique_ptr.cpp new file mode 100644 index 0000000..0b4e80b --- /dev/null +++ b/test/unit/small_unique_ptr.cpp @@ -0,0 +1,425 @@ +// +// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/capy +// + +#include +#include +#include +#include + +#include "test_suite.hpp" + +template +class small_unique_ptr +{ + static_assert(N >= sizeof(T), "Buffer too small for base type"); + + template + struct fits_in_buffer + : std::integral_constant< + bool, + (sizeof(U) <= N) && + (alignof(U) <= alignof(std::max_align_t))> + { + }; + + alignas(std::max_align_t) unsigned char buffer_[N]; + T* ptr_; + void (*destroy_)(small_unique_ptr*); + void (*relocate_)(small_unique_ptr*, small_unique_ptr*); + + template + static void destroy_small(small_unique_ptr* self) + { + static_cast(self->ptr_)->~U(); + } + + template + static void destroy_large(small_unique_ptr* self) + { + delete static_cast(self->ptr_); + } + + template + static void relocate_small( + small_unique_ptr* src, + small_unique_ptr* dst) + { + static_assert( + std::is_nothrow_move_constructible::value, + "U must be nothrow move constructible for SBO"); + U* p = static_cast(src->ptr_); + dst->ptr_ = ::new(static_cast(&dst->buffer_)) U(std::move(*p)); + dst->destroy_ = src->destroy_; + dst->relocate_ = src->relocate_; + p->~U(); + src->ptr_ = nullptr; + src->destroy_ = nullptr; + src->relocate_ = nullptr; + } + + template + static void relocate_large( + small_unique_ptr* src, + small_unique_ptr* dst) + { + dst->ptr_ = src->ptr_; + dst->destroy_ = src->destroy_; + dst->relocate_ = src->relocate_; + src->ptr_ = nullptr; + src->destroy_ = nullptr; + src->relocate_ = nullptr; + } + + void clear() noexcept + { + ptr_ = nullptr; + destroy_ = nullptr; + relocate_ = nullptr; + } + +public: + typedef T element_type; + typedef T* pointer; + + small_unique_ptr() noexcept + : ptr_(nullptr) + , destroy_(nullptr) + , relocate_(nullptr) + { + } + + small_unique_ptr(std::nullptr_t) noexcept + : small_unique_ptr() + { + } + + template::value>::type> + explicit + small_unique_ptr( + U* p) noexcept + : ptr_(p) + , destroy_(p ? &destroy_large : nullptr) + , relocate_(p ? &relocate_large : nullptr) + { + } + + small_unique_ptr(small_unique_ptr const&) = delete; + small_unique_ptr& operator=(small_unique_ptr const&) = delete; + + small_unique_ptr(small_unique_ptr&& other) noexcept + : ptr_(nullptr) + , destroy_(nullptr) + , relocate_(nullptr) + { + if(other.ptr_) + other.relocate_(&other, this); + } + + small_unique_ptr& operator=(small_unique_ptr&& other) noexcept + { + if(this != &other) + { + reset(); + if(other.ptr_) + other.relocate_(&other, this); + } + return *this; + } + + small_unique_ptr& operator=(std::nullptr_t) noexcept + { + reset(); + return *this; + } + + ~small_unique_ptr() noexcept + { + reset(); + } + + void reset() noexcept + { + if(ptr_) + { + destroy_(this); + clear(); + } + } + + pointer release() noexcept + { + pointer p = ptr_; + clear(); + return p; + } + + pointer get() const noexcept + { + return ptr_; + } + + explicit operator bool() const noexcept + { + return ptr_ != nullptr; + } + + typename std::add_lvalue_reference::type + operator*() const noexcept + { + return *ptr_; + } + + pointer operator->() const noexcept + { + return ptr_; + } + + void swap(small_unique_ptr& other) noexcept + { + small_unique_ptr tmp(std::move(other)); + other = std::move(*this); + *this = std::move(tmp); + } + + // SBO path + template + static + typename std::enable_if< + fits_in_buffer::value && + std::is_convertible::value, + small_unique_ptr>::type + emplace(Args&&... args) + { + static_assert( + std::is_nothrow_move_constructible::value, + "U must be nothrow move constructible for SBO"); + small_unique_ptr p; + p.ptr_ = ::new(static_cast(&p.buffer_)) U( + std::forward(args)...); + p.destroy_ = &destroy_small; + p.relocate_ = &relocate_small; + return p; + } + + // Heap path + template + static + typename std::enable_if< + !fits_in_buffer::value && + std::is_convertible::value, + small_unique_ptr>::type + emplace(Args&&... args) + { + small_unique_ptr p; + p.ptr_ = new U(std::forward(args)...); + p.destroy_ = &destroy_large; + p.relocate_ = &relocate_large; + return p; + } +}; + +template +void swap( + small_unique_ptr& lhs, + small_unique_ptr& rhs) noexcept +{ + lhs.swap(rhs); +} + +template +typename std::enable_if< + std::is_convertible::value, + small_unique_ptr>::type +make_small_unique(Args&&... args) +{ + return small_unique_ptr::template emplace( + std::forward(args)...); +} + +//----------------------------------------------- + +struct Base +{ + virtual ~Base() = default; + virtual int value() const = 0; +}; + +struct Small : Base +{ + int x; + Small(int v) : x(v) {} + int value() const override { return x; } +}; + +struct Large : Base +{ + int data[64]; + Large(int v) { data[0] = v; } + int value() const override { return data[0]; } +}; + +static int destroyed = 0; + +struct Tracked : Base +{ + int x; + Tracked(int v) : x(v) {} + ~Tracked() { ++destroyed; } + int value() const override { return x; } +}; + +struct TrackedLarge : Base +{ + int data[64]; + TrackedLarge(int v) { data[0] = v; } + ~TrackedLarge() { ++destroyed; } + int value() const override { return data[0]; } +}; + +namespace boost { +namespace capy { + +struct small_unique_ptr_test +{ + void + run() + { + // default and nullptr construction + small_unique_ptr p1; + small_unique_ptr p2(nullptr); + BOOST_TEST(!p1); + BOOST_TEST(!p2); + BOOST_TEST(p1.get() == nullptr); + + // SBO construction (Small fits in buffer) + auto p3 = make_small_unique(42); + BOOST_TEST(p3); + BOOST_TEST(p3->value() == 42); + BOOST_TEST((*p3).value() == 42); + + // heap construction via make_small_unique (Large doesn't fit) + auto p3b = make_small_unique(88); + BOOST_TEST(p3b); + BOOST_TEST(p3b->value() == 88); + + // heap construction via raw pointer (Large doesn't fit) + small_unique_ptr p4(new Large(99)); + BOOST_TEST(p4); + BOOST_TEST(p4->value() == 99); + + // move construction (SBO) + auto p5 = std::move(p3); + BOOST_TEST(p5); + BOOST_TEST(!p3); + BOOST_TEST(p5->value() == 42); + + // move construction (heap) + auto p5b = std::move(p3b); + BOOST_TEST(p5b); + BOOST_TEST(!p3b); + BOOST_TEST(p5b->value() == 88); + + // move assignment (SBO) + p3 = std::move(p5); + BOOST_TEST(p3); + BOOST_TEST(!p5); + BOOST_TEST(p3->value() == 42); + + // move assignment (heap) + p3b = std::move(p5b); + BOOST_TEST(p3b); + BOOST_TEST(!p5b); + BOOST_TEST(p3b->value() == 88); + + // nullptr assignment + p3 = nullptr; + BOOST_TEST(!p3); + + // reset and destruction tracking (SBO) + destroyed = 0; + { + auto p = make_small_unique(1); + BOOST_TEST(destroyed == 0); + p.reset(); + BOOST_TEST(destroyed == 1); + BOOST_TEST(!p); + } + BOOST_TEST(destroyed == 1); + + // destructor (SBO) + destroyed = 0; + { + auto p = make_small_unique(2); + } + BOOST_TEST(destroyed == 1); + + // destructor (heap via raw pointer) + destroyed = 0; + { + small_unique_ptr p(new Tracked(3)); + } + BOOST_TEST(destroyed == 1); + + // destructor (heap via make_small_unique) + destroyed = 0; + { + auto p = make_small_unique(4); + } + BOOST_TEST(destroyed == 1); + + // swap (SBO) + auto pa = make_small_unique(10); + auto pb = make_small_unique(20); + swap(pa, pb); + BOOST_TEST(pa->value() == 20); + BOOST_TEST(pb->value() == 10); + + // swap (heap) + auto pc = make_small_unique(30); + auto pd = make_small_unique(40); + swap(pc, pd); + BOOST_TEST(pc->value() == 40); + BOOST_TEST(pd->value() == 30); + + // self-move-assignment (should be safe) + auto p6 = make_small_unique(77); +#if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 13) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wself-move" +#endif + p6 = std::move(p6); +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + BOOST_TEST(p6); + BOOST_TEST(p6->value() == 77); + + // release (heap) + small_unique_ptr p7(new Large(55)); + Base* raw = p7.release(); + BOOST_TEST(!p7); + BOOST_TEST(raw->value() == 55); + delete raw; + + // move from empty + small_unique_ptr empty; + auto p8 = std::move(empty); + BOOST_TEST(!p8); + BOOST_TEST(!empty); + } +}; + +TEST_SUITE( + small_unique_ptr_test, + "boost.capy.small_unique_ptr"); + +} // capy +} // boost \ No newline at end of file