Compare commits

...
7 Commits
3 changed files with 72 additions and 8 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.
+21 -7
View File
@@ -23,11 +23,15 @@
#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
@@ -56,6 +60,16 @@ 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)
@@ -113,7 +127,7 @@ 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;
@@ -275,24 +289,24 @@ 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; \
} \ } \
} \ } \
int rcp_count() const \ int rcp_count() const \
{ \ { \
return ref_count.load(std::memory_order_relaxed); \ return rcp_ref_count.load(std::memory_order_relaxed); \
} \ } \
private: \ template <typename RCP_T_> friend class rcp; \
mutable std::atomic<int> ref_count{0}; \
rcp_managed(classname) rcp_managed(classname)
#define rcp_managed(classname) \ #define rcp_managed(classname) \
+42
View File
@@ -413,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;
@@ -427,6 +448,25 @@ 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();
@@ -454,6 +494,8 @@ int main(int argc, char * argv[])
test_use_count(); 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;
} }