Compare commits

...
14 Commits
3 changed files with 186 additions and 10 deletions

No files matched your search

+9 -1
View File
@@ -319,6 +319,7 @@ 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`) |
@@ -326,7 +327,14 @@ 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` |
The intrusive design means each managed object requires only one heap Because `rcp` stores only a single raw pointer, `sizeof(rcp<T>)` equals
`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.
+71 -9
View File
@@ -28,6 +28,9 @@
/** /**
* 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
@@ -81,7 +84,7 @@ public:
rcp & operator=(rcp && other) noexcept rcp & operator=(rcp && other) noexcept
{ {
if (ptr != other.ptr) if (this != &other)
{ {
if (ptr) if (ptr)
{ {
@@ -113,12 +116,17 @@ public:
} }
template <typename U> template <typename U>
rcp(rcp<U> && other) : ptr(static_cast<T *>(other.ptr)) rcp(rcp<U> && other) noexcept : 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;
@@ -167,6 +175,38 @@ 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;
@@ -187,6 +227,18 @@ 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>
@@ -197,6 +249,12 @@ 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>
@@ -220,27 +278,31 @@ rcp<T> rcp_dynamic_cast(rcp<U> && other)
} }
#define rcp_managed_root(classname) \ #define rcp_managed_root(classname) \
public: \ private: \
mutable std::atomic<int> rcp_ref_count{0}; \
void rcp_inc() const \ void rcp_inc() const \
{ \ { \
ref_count.fetch_add(1, std::memory_order_relaxed); \ rcp_ref_count.fetch_add(1, std::memory_order_relaxed); \
} \ } \
void rcp_dec() const \ void rcp_dec() const \
{ \ { \
if (ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) \ if (rcp_ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) \
{ \ { \
delete this; \ delete this; \
} \ } \
} \ } \
private: \ int rcp_count() const \
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() \ rcp<classname> get_rcp() const \
{ \ { \
return rcp<classname>(this); \ return rcp<classname>(const_cast<classname *>(this)); \
} \ } \
template <typename... Args> \ template <typename... Args> \
static rcp<classname> create(Args&&... args) \ static rcp<classname> create(Args&&... args) \
+106
View File
@@ -1,5 +1,6 @@
#include <rcp.h> #include <rcp.h>
#include <cassert> #include <cassert>
#include <map>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
@@ -83,6 +84,8 @@ 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()
@@ -111,6 +114,16 @@ 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;
@@ -150,11 +163,29 @@ 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()
@@ -190,6 +221,18 @@ 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;
@@ -314,6 +357,39 @@ 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);
@@ -321,6 +397,9 @@ 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()
@@ -334,6 +413,27 @@ 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;
@@ -354,13 +454,16 @@ int main(int argc, char * argv[])
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();
@@ -368,8 +471,11 @@ 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();
return 0; return 0;
} }