Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 36 additions & 13 deletions modules/strong_ptr.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ module;
#include <cstddef>
#include <cstdint>


#include <array>
#include <atomic>
#include <exception>
#include <memory>
#include <memory_resource>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -284,11 +282,14 @@ struct rc
if (p_object != nullptr) {
// Cast back into the original rc<T> type and ...
auto const* obj = static_cast<rc<T> const*>(p_object);
// Destruct T
// Destruct T.
obj->m_object.~T();
// Destructor ref_info:
// needed if we use strong_ptr<std::pmr::memory_resource>
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<ref_info>) {
obj->m_info.~ref_info();
}
}
// Return size for future deallocation
return sizeof(rc<T>);
Expand Down Expand Up @@ -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<class U>
friend class enable_strong_from_this;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
97 changes: 97 additions & 0 deletions tests/strong_ptr.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_class>(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<int> 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<test_class, 2> 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<outer_class>(test_allocator, 42);

// Create aliases to inner members
strong_ptr<test_class> inner_alias(outer, &outer_class::inner);
strong_ptr<test_class> 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<derived_class>(test_allocator, 99);

// Convert to base class pointer
strong_ptr<base_class> 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 = []() {
Expand Down