Compare commits

..

No commits in common. "main" and "readme-wip" have entirely different histories.

3 changed files with 14 additions and 221 deletions

View File

@ -319,7 +319,6 @@ for (auto & l : g_listeners) l->events_received++;
| | `rcp` | `std::shared_ptr` | | | `rcp` | `std::shared_ptr` |
|---|---|---| |---|---|---|
| Pointer size | One pointer | Two pointers (object + control block) |
| Reference count location | Inside the object | Separate control block (extra allocation) | | Reference count location | Inside the object | Separate control block (extra allocation) |
| Class requirement | `rcp_managed_root` / `rcp_managed` macro | None (any type) | | Class requirement | `rcp_managed_root` / `rcp_managed` macro | None (any type) |
| Weak pointers | No | Yes (`std::weak_ptr`) | | Weak pointers | No | Yes (`std::weak_ptr`) |
@ -327,14 +326,7 @@ for (auto & l : g_listeners) l->events_received++;
| Pointer to `this` | implicit conversion from `this` or explicit `get_rcp()` | `std::enable_shared_from_this` | | Pointer to `this` | implicit conversion from `this` or explicit `get_rcp()` | `std::enable_shared_from_this` |
| Checked downcast | `rcp_dynamic_cast` | `std::dynamic_pointer_cast` | | Checked downcast | `rcp_dynamic_cast` | `std::dynamic_pointer_cast` |
Because `rcp` stores only a single raw pointer, `sizeof(rcp<T>)` equals The intrusive design means each managed object requires only one heap
`sizeof(T *)` — typically 8 bytes on a 64-bit platform.
`std::shared_ptr` stores both a pointer to the object and a pointer to the
control block, so `sizeof(std::shared_ptr<T>)` is typically 16 bytes.
This makes `rcp` more efficient to pass by value, copy, and store in
containers.
The intrusive design also means each managed object requires only one heap
allocation instead of two which can improve performance for many applications. allocation instead of two which can improve performance for many applications.
The trade-off is that the class must be written with `rcp` in mind. The trade-off is that the class must be written with `rcp` in mind.

View File

@ -23,15 +23,11 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <cstddef>
#include <functional> #include <functional>
#include <type_traits> #include <type_traits>
/** /**
* Reference-counting pointer. * Reference-counting pointer.
*
* See https://github.com/holtrop/rcp for documentation, issues, and pull
* requests.
*/ */
template <typename T> template <typename T>
class rcp class rcp
@ -60,16 +56,6 @@ public:
} }
} }
rcp & operator=(nullptr_t) noexcept
{
if (ptr)
{
ptr->rcp_dec();
ptr = nullptr;
}
return *this;
}
rcp & operator=(const rcp & other) rcp & operator=(const rcp & other)
{ {
if (ptr != other.ptr) if (ptr != other.ptr)
@ -95,7 +81,7 @@ public:
rcp & operator=(rcp && other) noexcept rcp & operator=(rcp && other) noexcept
{ {
if (this != &other) if (ptr != other.ptr)
{ {
if (ptr) if (ptr)
{ {
@ -127,17 +113,12 @@ public:
} }
template <typename U> template <typename U>
rcp(rcp<U> && other) noexcept : ptr(static_cast<T *>(other.ptr)) rcp(rcp<U> && other) : ptr(static_cast<T *>(other.ptr))
{ {
static_assert(std::is_base_of<T, U>::value, "rcp: implicit cast must be an upcast"); static_assert(std::is_base_of<T, U>::value, "rcp: implicit cast must be an upcast");
other.ptr = nullptr; other.ptr = nullptr;
} }
int use_count() const
{
return ptr ? ptr->rcp_count() : 0;
}
void swap(rcp & other) noexcept void swap(rcp & other) noexcept
{ {
T * tmp = ptr; T * tmp = ptr;
@ -186,38 +167,6 @@ public:
return static_cast<const void *>(ptr) != static_cast<const void *>(other.ptr); return static_cast<const void *>(ptr) != static_cast<const void *>(other.ptr);
} }
template <typename U>
bool operator<(const rcp<U> & other) const
{
return std::less<const void *>()(
static_cast<const void *>(ptr),
static_cast<const void *>(other.ptr));
}
template <typename U>
bool operator<=(const rcp<U> & other) const
{
return !std::less<const void *>()(
static_cast<const void *>(other.ptr),
static_cast<const void *>(ptr));
}
template <typename U>
bool operator>(const rcp<U> & other) const
{
return std::less<const void *>()(
static_cast<const void *>(other.ptr),
static_cast<const void *>(ptr));
}
template <typename U>
bool operator>=(const rcp<U> & other) const
{
return !std::less<const void *>()(
static_cast<const void *>(ptr),
static_cast<const void *>(other.ptr));
}
bool operator==(std::nullptr_t) const bool operator==(std::nullptr_t) const
{ {
return ptr == nullptr; return ptr == nullptr;
@ -238,18 +187,6 @@ public:
friend rcp<V> rcp_dynamic_cast(rcp<W> && other); friend rcp<V> rcp_dynamic_cast(rcp<W> && other);
}; };
template <typename T>
bool operator==(std::nullptr_t, const rcp<T> & p)
{
return p == nullptr;
}
template <typename T>
bool operator!=(std::nullptr_t, const rcp<T> & p)
{
return p != nullptr;
}
namespace std namespace std
{ {
template <typename T> template <typename T>
@ -260,12 +197,6 @@ namespace std
return hash<const void *>()(static_cast<const void *>(p.get_raw())); return hash<const void *>()(static_cast<const void *>(p.get_raw()));
} }
}; };
template <typename T>
void swap(rcp<T> & a, rcp<T> & b) noexcept
{
a.swap(b);
}
} }
template <typename T, typename U> template <typename T, typename U>
@ -289,31 +220,27 @@ rcp<T> rcp_dynamic_cast(rcp<U> && other)
} }
#define rcp_managed_root(classname) \ #define rcp_managed_root(classname) \
private: \ public: \
mutable std::atomic<int> rcp_ref_count{0}; \
void rcp_inc() const \ void rcp_inc() const \
{ \ { \
rcp_ref_count.fetch_add(1, std::memory_order_relaxed); \ ref_count.fetch_add(1, std::memory_order_relaxed); \
} \ } \
void rcp_dec() const \ void rcp_dec() const \
{ \ { \
if (rcp_ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) \ if (ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) \
{ \ { \
delete this; \ delete this; \
} \ } \
} \ } \
int rcp_count() const \ private: \
{ \ mutable std::atomic<int> ref_count{0}; \
return rcp_ref_count.load(std::memory_order_relaxed); \
} \
template <typename RCP_T_> friend class rcp; \
rcp_managed(classname) rcp_managed(classname)
#define rcp_managed(classname) \ #define rcp_managed(classname) \
public: \ public: \
rcp<classname> get_rcp() const \ rcp<classname> get_rcp() \
{ \ { \
return rcp<classname>(const_cast<classname *>(this)); \ return rcp<classname>(this); \
} \ } \
template <typename... Args> \ template <typename... Args> \
static rcp<classname> create(Args&&... args) \ static rcp<classname> create(Args&&... args) \

126
tests.cpp
View File

@ -1,6 +1,5 @@
#include <rcp.h> #include <rcp.h>
#include <cassert> #include <cassert>
#include <map>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@ -84,8 +83,6 @@ void test_booleans()
rcp<MyDerived> myderived; rcp<MyDerived> myderived;
assert(!myderived); assert(!myderived);
assert(myderived == nullptr); assert(myderived == nullptr);
assert(nullptr != mybase);
assert(nullptr == myderived);
} }
void test_create() void test_create()
@ -114,16 +111,6 @@ struct Receiver
} }
}; };
void test_get_rcp_const()
{
auto a = MyObj::create();
const MyObj & ref = *a;
rcp<MyObj> b = ref.get_rcp();
assert(b);
assert(b->v == 42);
assert(b.use_count() == 2);
}
void test_multi_construct_from_raw_pointers() void test_multi_construct_from_raw_pointers()
{ {
Receiver r; Receiver r;
@ -164,28 +151,10 @@ void test_listener_self_registration()
void test_copy_assignment_decrements_previous_reference() void test_copy_assignment_decrements_previous_reference()
{ {
int constructed_before = mybase_construct;
int destructed_before = mybase_destruct;
{
MyB myb = MyB::create(12, 13); MyB myb = MyB::create(12, 13);
MyB myb2 = MyB::create(14, 15); MyB myb2 = MyB::create(14, 15);
myb = myb2; myb = myb2;
assert(myb->x == 14); assert(myb->x == 14);
assert(mybase_destruct == destructed_before + 1);
}
assert(mybase_construct == constructed_before + 2);
assert(mybase_destruct == destructed_before + 2);
}
void test_self_assignment()
{
int destructed_before = mybase_destruct;
MyB a = MyB::create(1, 2);
a = a;
assert(a);
assert(a->x == 1);
assert(a.use_count() == 1);
assert(mybase_destruct == destructed_before);
} }
void test_move_constructor() void test_move_constructor()
@ -221,18 +190,6 @@ void test_move_assignment()
assert(mybase_destruct == destructed_before + 1); assert(mybase_destruct == destructed_before + 1);
} }
void test_move_assignment_same_object()
{
MyB a = MyB::create(1, 2);
MyB b = a;
assert(a.use_count() == 2);
b = std::move(a);
assert(!a);
assert(b);
assert(b->x == 1);
assert(b.use_count() == 1);
}
void test_move_assignment_releases_existing() void test_move_assignment_releases_existing()
{ {
int constructed_before = mybase_construct; int constructed_before = mybase_construct;
@ -357,39 +314,6 @@ protected:
~Counter() { external_destruct++; } ~Counter() { external_destruct++; }
}; };
void test_ordering()
{
MyB a = MyB::create(1, 2);
MyB b = MyB::create(3, 4);
MyB a2 = a;
assert(!(a < a2));
assert(a <= a2);
assert(!(a > a2));
assert(a >= a2);
assert((a < b) != (b < a));
assert((a < b) == (b > a));
assert((a <= b) == (b >= a));
std::map<MyB, int> m;
m[a] = 1;
m[b] = 2;
assert(m[a2] == 1);
assert(m[b] == 2);
}
void test_use_count()
{
MyB a = MyB::create(1, 2);
assert(a.use_count() == 1);
{
MyB b = a;
assert(a.use_count() == 2);
assert(b.use_count() == 2);
}
assert(a.use_count() == 1);
MyB empty;
assert(empty.use_count() == 0);
}
void test_swap() void test_swap()
{ {
MyB a = MyB::create(1, 2); MyB a = MyB::create(1, 2);
@ -397,9 +321,6 @@ void test_swap()
a.swap(b); a.swap(b);
assert(a->x == 3); assert(a->x == 3);
assert(b->x == 1); assert(b->x == 1);
std::swap(a, b);
assert(a->x == 1);
assert(b->x == 3);
} }
void test_hash() void test_hash()
@ -413,27 +334,6 @@ void test_hash()
assert(map.find(c) == map.end()); assert(map.find(c) == map.end());
} }
template <typename T>
struct Box
{
rcp_managed_root(Box);
protected:
Box(T v) : v(v) {}
public:
T v;
};
void test_class_template()
{
auto a = Box<int>::create(42);
auto b = Box<int>::create(99);
assert(a->v == 42);
assert(b->v == 99);
auto c = a;
assert(c->v == 42);
assert(c.use_count() == 2);
}
void test_external_class() void test_external_class()
{ {
int before = external_destruct; int before = external_destruct;
@ -448,41 +348,19 @@ void test_external_class()
assert(external_destruct == before + 2); assert(external_destruct == before + 2);
} }
void test_assign_nullptr()
{
int constructed_before = mybase_construct;
int destructed_before = mybase_destruct;
{
rcp<MyBase> base = MyBase::create(6, 6);
assert(mybase_construct == constructed_before + 1);
assert(mybase_destruct == destructed_before);
base = nullptr;
assert(mybase_construct == constructed_before + 1);
assert(mybase_destruct == destructed_before + 1);
base = nullptr;
assert(mybase_construct == constructed_before + 1);
assert(mybase_destruct == destructed_before + 1);
}
assert(mybase_construct == constructed_before + 1);
assert(mybase_destruct == destructed_before + 1);
}
int main(int argc, char * argv[]) int main(int argc, char * argv[])
{ {
test_class_hierarchy(); test_class_hierarchy();
test_dereference(); test_dereference();
test_booleans(); test_booleans();
test_create(); test_create();
test_get_rcp_const();
test_multi_construct_from_raw_pointers(); test_multi_construct_from_raw_pointers();
test_listener_self_registration(); test_listener_self_registration();
test_copy_assignment_decrements_previous_reference(); test_copy_assignment_decrements_previous_reference();
test_self_assignment();
test_cross_type_comparison(); test_cross_type_comparison();
test_reset(); test_reset();
test_move_constructor(); test_move_constructor();
test_move_assignment(); test_move_assignment();
test_move_assignment_same_object();
test_move_assignment_releases_existing(); test_move_assignment_releases_existing();
test_upcast(); test_upcast();
test_move_upcast(); test_move_upcast();
@ -490,12 +368,8 @@ int main(int argc, char * argv[])
test_dynamic_cast_failure(); test_dynamic_cast_failure();
test_dynamic_cast_move_success(); test_dynamic_cast_move_success();
test_dynamic_cast_move_failure(); test_dynamic_cast_move_failure();
test_ordering();
test_use_count();
test_swap(); test_swap();
test_hash(); test_hash();
test_class_template();
test_external_class(); test_external_class();
test_assign_nullptr();
return 0; return 0;
} }