From 042bf3cbe4ab3ea6cbb97158e9767893436d300c Mon Sep 17 00:00:00 2001 From: Khalil Estell Date: Mon, 22 Dec 2025 13:24:59 -0800 Subject: [PATCH] :sparkles: Add `strong_ptr::get_allocator()` Provides access to the allocator like `std::vector::get_allocator()` Resolves #38 --- modules/strong_ptr.cppm | 49 ++++++++++++++------ tests/strong_ptr.test.cpp | 97 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 13 deletions(-) diff --git a/modules/strong_ptr.cppm b/modules/strong_ptr.cppm index e010290..7b73766 100644 --- a/modules/strong_ptr.cppm +++ b/modules/strong_ptr.cppm @@ -17,9 +17,7 @@ module; #include #include - #include -#include #include #include #include @@ -163,15 +161,15 @@ struct ref_info using type_erased_destruct_function_t = std::size_t(void const*); /// Initialize to 1 since creation implies a reference - std::pmr::memory_resource* allocator = nullptr; + std::pmr::memory_resource* allocator; type_erased_destruct_function_t* destroy = nullptr; int strong_count = 0; int weak_count = 0; // Add explicit constructor to avoid aggregate initialization issues - constexpr ref_info(std::pmr::memory_resource* p_alloc, + constexpr ref_info(std::pmr::memory_resource* p_allocator, type_erased_destruct_function_t* p_destroy) - : allocator(p_alloc) + : allocator(p_allocator) , destroy(p_destroy) , strong_count(0) , weak_count(0) @@ -284,11 +282,14 @@ struct rc if (p_object != nullptr) { // Cast back into the original rc type and ... auto const* obj = static_cast const*>(p_object); - // Destruct T + // Destruct T. obj->m_object.~T(); - // Destructor ref_info: - // needed if we use strong_ptr - obj->m_info.~ref_info(); + // Destructor ref_info if its not trivially destructible. In general, this + // should never be the case, but if we do modify ref_info to have a + // non-trivial destructor, this will automatically manage that. + if constexpr (not std::is_trivially_destructible_v) { + obj->m_info.~ref_info(); + } } // Return size for future deallocation return sizeof(rc); @@ -880,6 +881,21 @@ public: return m_ctrl != nullptr; } + /** + * @brief Get the allocator used to allocate this object + * + * @return constexpr std::pmr::memory_resource* - the allocator used to + * allocate this object. Returns `nullptr` if the object was statically + * allocated. + */ + constexpr std::pmr::memory_resource* get_allocator() const noexcept + { + if (m_ctrl == nullptr) { + return nullptr; + } + return m_ctrl->allocator; + } + private: template friend class enable_strong_from_this; @@ -1953,11 +1969,16 @@ private: * @brief Factory function to create a strong_ptr with automatic construction * detection * - * This is the primary way to create a new strong_ptr. It automatically detects - * whether the target type requires token-based construction (for classes that - * should only be managed by strong_ptr) or supports normal construction. + * This is the primary way to create a new strong_ptr. + * + * To create a strong_ptr, you must provide a `std::pmr::memory_resource`. + * That resource must outlive all strong_ptrs created by it. To uphold the + * guarantees of strong_ptr, the `std::pmr::memory_resource` MUST terminate the + * application if it is destroyed before all of its allocated memory has been + * freed. This requirement prevents use-after-free errors. * * The function performs the following operations: + * * 1. Allocates memory for both the object and its control block together * 2. Detects at compile time if the type expects a strong_ptr_only_token * 3. Constructs the object with appropriate parameters @@ -2006,7 +2027,9 @@ private: * * @tparam T The type of object to create * @tparam Args Types of arguments to forward to the constructor - * @param p_memory_resource memory resource used to allocate object + * @param p_memory_resource the memory resource used to allocate memory for the + * strong_ptr. The memory resource must call `std::terminate` if it is destroyed + * without all of its memory being freed. * @param p_args Arguments to forward to the constructor * @return A strong_ptr managing the newly created object * @throws Any exception thrown by the object's constructor diff --git a/tests/strong_ptr.test.cpp b/tests/strong_ptr.test.cpp index 19ec1c7..575f960 100644 --- a/tests/strong_ptr.test.cpp +++ b/tests/strong_ptr.test.cpp @@ -326,6 +326,103 @@ boost::ut::suite<"strong_ptr_only_test"> strong_ptr_only_test = []() { << "Static object should still be accessible after strong_ptr " "destruction"; }; + + "get_allocator_for_dynamic_allocation"_test = [&] { + // Create a dynamically allocated strong_ptr + auto ptr = make_strong_ptr(test_allocator, 42); + + // Get the allocator + auto* allocator = ptr.get_allocator(); + + // Verify the allocator matches the one used for creation + expect(that % test_allocator == allocator) + << "Allocator should match the one used for creation"; + + // Verify it's not nullptr for dynamically allocated objects + expect(allocator != nullptr) + << "Allocator should not be nullptr for dynamically allocated objects"; + + // Test that copies return the same allocator + auto ptr_copy = ptr; + expect(that % test_allocator == ptr_copy.get_allocator()) + << "Copied strong_ptr should return the same allocator"; + }; + + "get_allocator_for_static_allocation"_test = [&] { + // Create a static object (using int to avoid affecting test_class instance count) + static int static_obj = 777; + + // Create strong_ptr to static object using unsafe_assume_static_tag + strong_ptr ptr(mem::unsafe_assume_static_tag{}, static_obj); + + // Get the allocator + auto* allocator = ptr.get_allocator(); + + // Verify the allocator is nullptr for statically allocated objects + expect(allocator == nullptr) + << "Allocator should be nullptr for statically allocated objects"; + + // Test that copies also return nullptr + auto ptr_copy = ptr; + expect(ptr_copy.get_allocator() == nullptr) + << "Copied static strong_ptr should also return nullptr allocator"; + }; + + "get_allocator_for_aliased_ptr"_test = [&] { + // Create a class with internal structure + struct outer_class + { + test_class inner; + std::array array_inner; + explicit outer_class(int p_value) + : inner(p_value) + , array_inner{ test_class{ p_value }, test_class{ p_value } } + { + } + }; + + // Create the outer object with a specific allocator + auto outer = make_strong_ptr(test_allocator, 42); + + // Create aliases to inner members + strong_ptr inner_alias(outer, &outer_class::inner); + strong_ptr array_alias(outer, &outer_class::array_inner, 0); + + // Verify all aliases return the same allocator as the parent + expect(that % test_allocator == outer.get_allocator()) + << "Outer strong_ptr should return the original allocator"; + + expect(that % test_allocator == inner_alias.get_allocator()) + << "Inner alias should return the same allocator as parent"; + + expect(that % test_allocator == array_alias.get_allocator()) + << "Array alias should return the same allocator as parent"; + + // Verify they all return the same allocator instance + expect(outer.get_allocator() == inner_alias.get_allocator()) + << "Outer and inner alias should share the same allocator"; + + expect(outer.get_allocator() == array_alias.get_allocator()) + << "Outer and array alias should share the same allocator"; + }; + + "get_allocator_for_polymorphic_ptr"_test = [&] { + // Create a derived class object + auto derived = make_strong_ptr(test_allocator, 99); + + // Convert to base class pointer + strong_ptr base = derived; + + // Both should return the same allocator + expect(that % test_allocator == derived.get_allocator()) + << "Derived strong_ptr should return the original allocator"; + + expect(that % test_allocator == base.get_allocator()) + << "Base strong_ptr should return the same allocator"; + + expect(derived.get_allocator() == base.get_allocator()) + << "Derived and base pointers should share the same allocator"; + }; }; boost::ut::suite<"monotonic_allocator_test"> monotonic_allocator_test = []() {