Compare commits

...
37 Commits
Author SHA1 Message Date
josh b695ce7758 Add specialized operator=() to handle nullptr assignment 2026-02-27 14:50:48 -05:00
josh e3cdfb1bfa Add github link in rcp.h 2026-02-25 09:43:43 -05:00
josh 339f1ce9d6 README: note managed pointer size differences between rcp and shared_ptr 2026-02-25 09:30:59 -05:00
josh 771473b62a Rename template parameter to reduce chance of conflict 2026-02-25 09:22:39 -05:00
josh 3a624ee65b Rename ref_count to rcp_ref_count 2026-02-25 09:15:59 -05:00
josh 27420c507d Make rpc_inc() and rpc_dec() private 2026-02-25 09:15:01 -05:00
josh 93527f8647 Mark move constructor as noexcept 2026-02-25 09:08:50 -05:00
josh 7d5316bd79 Allow get_rcp() to be called on a const reference 2026-02-25 08:25:03 -05:00
josh 0de327d442 Test self assignment 2026-02-25 08:17:18 -05:00
josh 8d0243dcde Add free std::swap() specialization function for rcp 2026-02-25 08:16:33 -05:00
josh b87fbc0d76 Allow comparison to nullptr on left side of operator 2026-02-25 08:15:22 -05:00
josh d88922756d Improve test_copy_assignment_decrements_previous_reference() 2026-02-25 01:11:13 -05:00
josh ec937b2395 Fix move assignment from the same object 2026-02-25 00:49:33 -05:00
josh 17a647b897 Add ordering comparison operators 2026-02-25 00:19:04 -05:00
josh d9a9a88a24 Add rcp::use_count() 2026-02-24 23:42:56 -05:00
josh af0e0d0084 Add README contents 2026-02-24 23:11:38 -05:00
josh d2b6755704 Add swap() and support std::hash 2026-02-24 19:53:59 -05:00
josh 464527a34d Test adding rcp management to an external class via inheritance 2026-02-24 19:41:34 -05:00
josh 011460254d Test multiple C++ standards; move tests to top level 2026-02-24 18:06:16 -05:00
josh 403db54664 Add "listener registration" test to test passing "this" out of a managed class 2026-02-24 17:49:23 -05:00
josh 627661158e Add move version of rcp_dynamic_cast() 2026-02-24 17:17:34 -05:00
josh 69ecab39f2 Allow comparing rcp<T> to rcp<U> 2026-02-24 17:10:01 -05:00
josh 11555ded36 Add reset() 2026-02-24 17:07:07 -05:00
josh ddaae32525 Add casting move constructor 2026-02-24 16:00:14 -05:00
josh 7880adef89 Add static_assert to upcasting constructor; add rcp_dynamic_cast() 2026-02-24 15:48:41 -05:00
josh ce921e31be Use explicit operator bool() 2026-02-24 15:29:34 -05:00
josh 174a45cfe7 Use rcp instead of rcp<T> where possible 2026-02-24 15:23:22 -05:00
josh 62b729badd Add move constructor and move assignment operator 2026-02-24 15:18:41 -05:00
josh 2e30a9f407 Fix (and test) that copy constructor decrements previous object counter 2026-02-24 14:49:47 -05:00
josh 4a1a0c8855 Add operator==() and operator!=() 2026-02-24 14:44:38 -05:00
josh 47273edf6e Test construction from raw pointers 2026-02-23 23:01:56 -05:00
josh 21f2dd4cad Add rcp::create() 2026-02-23 22:52:07 -05:00
josh ffe8433d55 Add operator bool() and test boolean checks 2026-02-23 22:44:55 -05:00
josh be2da870bb Add and test dereference operators 2026-02-23 22:43:05 -05:00
josh 9dfb6e4740 Rename rcp_root() to rcp_managed_root() 2026-02-23 22:37:06 -05:00
josh 53b2247432 Avoid need for rcp_managed() call in rcp root class 2026-02-23 22:35:57 -05:00
josh 83ca3d8893 Add visibility attributes to rcp_managed() 2026-02-23 22:35:19 -05:00
7 changed files with 1109 additions and 93 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
/test/build/
/build/
+20 -4
View File
@@ -1,7 +1,23 @@
.PHONY: test
test:
$(MAKE) -C test
# This Makefile is entirely for running unit tests for rcp.h
INCLUDE := include
STANDARDS := 11 14 17 20 23
BINARIES := $(addprefix build/tests-, $(STANDARDS))
.PHONY: all
all: $(BINARIES)
@for std in $(STANDARDS); do \
echo "=== Test C++$$std ==="; \
build/tests-$$std; \
valgrind --leak-check=full --error-exitcode=1 \
--log-file=build/valgrind-$$std.log build/tests-$$std \
|| { echo "valgrind failed for C++$$std (see build/valgrind-$$std.log)"; exit 1; }; \
done
build/tests-%: tests.cpp $(INCLUDE)/rcp.h
@mkdir -p $(dir $@)
$(CXX) -std=c++$* -g -Wall -I$(INCLUDE) -o $@ $<
.PHONY: clean
clean:
$(MAKE) -C test clean
rm -rf build
+374 -1
View File
@@ -1,3 +1,376 @@
# rcp
`rcp` is a reference-counting pointer implementation for C++.
`rcp` is an intrusive reference-counting smart pointer for C++11(+).
In contrast to smart pointer implementations that use a separately allocated
control block with reference count (e.g. `std::shared_ptr`), the reference
count is stored inside the managed object itself, eliminating the separate heap
allocation that non-intrusive implementations require.
The managed object is automatically destructed when the last `rcp` pointing to
the object is destroyed.
Importantly, since the reference count is stored within the managed object,
this gives the object the freedom to pass its `this` pointer to any other
object or function which takes a `rcp` managed pointer and a new `rcp` managed
pointer can be automatically created from `this` to point to the same reference
count.
This is in contrast to `std::shared_ptr` or other smart pointer implementations
which store the reference count externally to the managed object, where
constructing another managed pointer from a raw `this` pointer can lead to a
double free situation with two managed pointers pointing at the same object.
The tradeoff is that the class definition must be modified to declare support
for being managed by an `rcp` smart pointer.
This does not mean, however, that class definitions which cannot be modified
cannot be managed by `rcp` smart pointers.
See the [Wrapping External Classes](#wrapping-external-classes) section for an
explanation of how to do this.
`rcp` is MIT-licensed.
See [https://github.com/holtrop/rcp](https://github.com/holtrop/rcp) for issues, releases, and pull requests.
## Installation
No installation is required to use `rcp`.
`rcp` is entirely implemented within one header file `rcp.h`.
Simply copy the header file (`rcp.h`) into your project or add the `rcp`
repository as a submodule and add the `include` directory to your build system
include path.
## Basic Usage
`rcp` behaves like a raw pointer for access:
```cpp
rcp<Dog> dog = Dog::create("Rex");
dog->bark();
std::cout << (*dog).name << "\n";
if (dog)
std::cout << "dog is valid\n";
// When `dog` goes out of scope and its destructor is called, it will decrement
// the reference count of the `Dog` instance. If this was the last reference
// to the object, the `Dog` instance will be deleted. Calling `reset()` on an
// instance of a `rcp` managed pointer will also decrement the reference count
// of the object currently being pointed to and then reset the reference to a
// null reference.
dog.reset();
```
## Defining Classes With rcp Support
Example:
```cpp
class Animal
{
rcp_managed_root(Animal);
protected:
Animal(std::string name) : name(std::move(name)) {}
virtual ~Animal() {}
public:
std::string name;
};
class Dog : public Animal
{
rcp_managed(Dog);
protected:
Dog(std::string name) : Animal(std::move(name)) {}
public:
void bark() { std::cout << "Woof!\n"; }
};
```
Add a call to the `rcp_managed_root` macro to the root of your class hierarchy.
The argument to the macro is the name of the class.
In a class hierarchy, there should be only one `rcp_managed_root` call at the
highest managed level.
Use `rcp_managed` for any classes deriving from a class using
`rcp_managed_root`.
`rcp_managed_root` injects the reference count and the increment/decrement
methods.
It also calls `rcp_managed` internally.
`rcp_managed` injects `create()` and `get_rcp()` into derived classes.
In this way, there will be only one reference count variable and one set of
increment/decrement functions defined in any object.
In contrast, the `get_rcp()` function will be defined within each class in the
hierarchy so that it returns a reference to the specific type that it is
called on.
Both macros end with a `private:` access specifier, so follow them with
`protected:` or `public:` as needed.
> **Note:** If the class will be inherited, its destructor must be marked
> `virtual`.
> Without this, destroying a derived object through a base class pointer
> will not call the derived destructor, leading to incomplete cleanup.
## Creating Objects
The managed object's constructor should generally be declared with `protected`
visibility instead of `public` visibility.
This prevents a user from constructing an object instance without a managed
pointer to it.
The `rcp_managed` macro defines a public `create()` static function which will
forward all arguments to the constructor and then call `get_rcp()` on the
resulting object to create the initial managed pointer to it.
In addition to the class itself defining a `create()` function, the `rcp`
managed pointer class also declares a `create()` function which can be used to
create an instance of the managed object.
See [Transparent Handles](#transparent-handles) for a way this can be used.
Users use the static `create()` method, which returns an `rcp` holding a
managed pointer to the newly constructed object:
```cpp
rcp<Animal> animal = Animal::create("Cat");
rcp<Dog> dog = Dog::create("Rex");
```
## Wrapping External Classes
`rcp` can add intrusive reference counting to a class you don't own and
cannot modify.
Derive from the external class and add `rcp_managed_root` to the derived
class.
You can give the derived class the same name inside your own namespace or a
different name if desired.
Consumers use your type and never interact with the external class directly.
Example:
```cpp
namespace external
{
class Counter
{
public:
int start;
int count;
Counter(int start) : start(start), count(start) {}
virtual ~Counter() {}
};
}
class Counter : public external::Counter
{
rcp_managed_root(Counter);
protected:
Counter(int start) : external::Counter(start) {}
~Counter() {}
};
auto a = Counter::create(10);
auto b = Counter::create(20);
a->count++; // direct access to external::Counter members
```
The derived class inherits all members of the external class.
`create()` forwards its arguments through to the external class constructor.
## Transparent Handles
Defining a `typedef` (or `using`) of `rcp<XImpl>` as `X` lets consumers
use `X` as if it were a plain value type, with no awareness of reference
counting or implementation classes.
Example:
```cpp
class ImageImpl
{
rcp_managed_root(ImageImpl);
protected:
ImageImpl(int width, int height) : width(width), height(height),
pixels(width * height) {}
~ImageImpl() {}
public:
int width, height;
std::vector<uint32_t> pixels;
};
typedef rcp<ImageImpl> Image;
```
Consumers work entirely with `Image` - creating, copying, and passing it
like a value, while the pixel data is shared automatically and freed when
no longer referenced.
```cpp
Image load_thumbnail(Image full) { return full; } // shares pixel data
Image icon = Image::create(16, 16);
Image copy = icon; // shared ownership, no pixel copy
Image thumb = load_thumbnail(icon); // still the same pixel data
icon.reset();
copy.reset();
// pixel data freed here, when thumb (the last holder) goes out of scope
```
This usage pattern is supported by `rcp` but is completely optional.
This usage pattern provides a more concise syntax, however it could be less
obvious to the user what is happening under the hood so it is entirely up to
the user whether or not to use this pattern.
## Copying and Moving
Copying an `rcp` increments the reference count.
Moving efficiently transfers ownership without touching the reference count,
and leaves the source null:
```cpp
rcp<Dog> a = Dog::create("Rex");
rcp<Dog> b = a; // a and b share ownership; refcount = 2
rcp<Dog> c = std::move(a); // c takes ownership from a; a is now null; refcount = 2
```
## Upcasting
Assigning an `rcp<Derived>` to an `rcp<Base>` is implicit.
Attempting a downcast this way is a compile error:
```cpp
rcp<Dog> dog = Dog::create("Rex");
rcp<Animal> animal = dog; // ok: implicit upcast
rcp<Dog> dog2 = animal; // error: implicit downcast not allowed
```
## Downcasting
Use `rcp_dynamic_cast` for explicit checked downcasts.
It returns a null `rcp` if the object is not of the target type.
Passing an rvalue transfers ownership on success and leaves the source
unchanged on failure:
```cpp
rcp<Animal> animal = Dog::create("Rex");
// copy downcast: animal remains valid regardless of outcome
rcp<Dog> dog = rcp_dynamic_cast<Dog>(animal);
// move downcast: animal is nulled on success, left intact on failure
rcp<Dog> dog2 = rcp_dynamic_cast<Dog>(std::move(animal));
```
## Comparison
```cpp
rcp<Dog> a = Dog::create("Rex");
rcp<Animal> b = a;
rcp<Dog> c = Dog::create("Buddy");
assert(a == b); // same object, different pointer types: ok
assert(a != c);
assert(c != nullptr);
```
## Getting an rcp from this
Use `get_rcp()` inside a member function to obtain a reference-counted
pointer to the current object:
```cpp
class Dog : public Animal
{
rcp_managed(Dog);
public:
rcp<Dog> self() { return get_rcp(); }
};
```
A managed class can also pass its raw `this` pointer directly to any
function or class method that accepts an `rcp`, and the existing reference
count will be used - no separate control block is created.
This is safe because the reference count is stored in the object itself.
With `std::shared_ptr`, in contrast, constructing from a raw `this` pointer
creates an independent control block, leading to a double-free when both reach
zero.
Example:
```cpp
struct EventListener;
static std::vector<rcp<EventListener>> g_listeners;
struct EventListener
{
rcp_managed_root(EventListener);
public:
int events_received = 0;
void attach() { g_listeners.push_back(this); }
};
auto a = EventListener::create();
auto b = EventListener::create();
a->attach();
b->attach();
for (auto & l : g_listeners) l->events_received++;
```
## Comparison with `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) |
| Class requirement | `rcp_managed_root` / `rcp_managed` macro | None (any type) |
| Weak pointers | No | Yes (`std::weak_ptr`) |
| Custom deleters | No | Yes |
| 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` |
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.
The trade-off is that the class must be written with `rcp` in mind.
Note that `std::enable_shared_from_this` is tied to the type it is inherited
from, so `shared_from_this()` always returns a `shared_ptr` to that base type.
A derived class method that needs a `shared_ptr` to its own type must
manually cast:
```cpp
// shared_ptr: verbose and requires knowing the base
std::shared_ptr<Dog> self()
{
return std::dynamic_pointer_cast<Dog>(shared_from_this());
}
```
With `rcp`, `rcp_managed` injects a `get_rcp()` that returns the correct
derived type directly:
```cpp
// rcp: returns rcp<Dog> directly, no cast needed
rcp<Dog> self() { return get_rcp(); }
```
## Comparison with `boost::intrusive_ptr`
Both are intrusive reference-counting pointers, but they differ in how
the reference counting is wired up:
| | `rcp` | `boost::intrusive_ptr` |
|---|---|---|
| Setup | `rcp_managed_root` / `rcp_managed` macros | User-defined `intrusive_ptr_add_ref` / `intrusive_ptr_release` free functions and ref-count storage |
| Upcasting | Implicit via constructor | Implicit via raw pointer conversion |
| Checked downcast | `rcp_dynamic_cast` | Not built in |
`rcp` automates the boilerplate of wiring up the reference count.
`boost::intrusive_ptr` can be more flexible for some uses cases because it can
wrap types that already have their own reference counting (e.g. COM objects,
OS handles) by implementing the free functions to call the existing mechanism.
+213 -14
View File
@@ -23,13 +23,21 @@
#pragma once
#include <atomic>
#include <cstddef>
#include <functional>
#include <type_traits>
/**
* Reference-counting pointer.
*
* See https://github.com/holtrop/rcp for documentation, issues, and pull
* requests.
*/
template <typename T>
class rcp
{
template <typename U> friend class rcp;
private:
T * ptr = nullptr;
@@ -52,13 +60,50 @@ public:
}
}
rcp<T> & operator=(const rcp & other)
rcp & operator=(nullptr_t) noexcept
{
if (ptr)
{
ptr->rcp_dec();
ptr = nullptr;
}
return *this;
}
rcp & operator=(const rcp & other)
{
if (ptr != other.ptr)
{
T * old_ptr = ptr;
ptr = other.ptr;
if (ptr)
{
ptr->rcp_inc();
}
if (old_ptr)
{
old_ptr->rcp_dec();
}
}
return *this;
}
rcp(rcp && other) noexcept : ptr(other.ptr)
{
other.ptr = nullptr;
}
rcp & operator=(rcp && other) noexcept
{
if (this != &other)
{
if (ptr)
{
ptr->rcp_dec();
}
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}
@@ -70,16 +115,45 @@ public:
}
}
/* Casting constructor: allows rcp<Base> -> rcp<Derived> */
/* Upcasting constructor: allows rcp<Derived> -> rcp<Base> */
template <typename U>
rcp(const rcp<U> & other) : ptr(static_cast<T *>(other.get_raw()))
rcp(const 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");
if (ptr)
{
ptr->rcp_inc();
}
}
template <typename U>
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");
other.ptr = nullptr;
}
int use_count() const
{
return ptr ? ptr->rcp_count() : 0;
}
void swap(rcp & other) noexcept
{
T * tmp = ptr;
ptr = other.ptr;
other.ptr = tmp;
}
void reset()
{
if (ptr)
{
ptr->rcp_dec();
ptr = nullptr;
}
}
T * get_raw() const
{
return ptr;
@@ -90,35 +164,160 @@ public:
return ptr;
}
bool operator!() const
T & operator*() const
{
return !ptr;
return *ptr;
}
explicit operator bool() const
{
return ptr != nullptr;
}
template <typename U>
bool operator==(const rcp<U> & other) const
{
return static_cast<const void *>(ptr) == static_cast<const void *>(other.ptr);
}
template <typename U>
bool operator!=(const rcp<U> & other) const
{
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
{
return ptr == nullptr;
}
bool operator!=(std::nullptr_t) const
{
return ptr != nullptr;
}
template <typename... Args>
static rcp create(Args&&... args)
{
return T::create(std::forward<Args>(args)...);
}
template <typename V, typename W>
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
{
template <typename T>
struct hash<rcp<T>>
{
size_t operator()(const rcp<T> & p) const noexcept
{
return hash<const void *>()(static_cast<const void *>(p.get_raw()));
}
};
#define rcp_root() \
public: \
template <typename T>
void swap(rcp<T> & a, rcp<T> & b) noexcept
{
a.swap(b);
}
}
template <typename T, typename U>
rcp<T> rcp_dynamic_cast(const rcp<U> & other)
{
return rcp<T>(dynamic_cast<T *>(other.get_raw()));
}
template <typename T, typename U>
rcp<T> rcp_dynamic_cast(rcp<U> && other)
{
T * p = dynamic_cast<T *>(other.ptr);
if (p)
{
other.ptr = nullptr;
rcp<T> result;
result.ptr = p;
return result;
}
return rcp<T>();
}
#define rcp_managed_root(classname) \
private: \
mutable std::atomic<int> rcp_ref_count{0}; \
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 \
{ \
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; \
} \
} \
private: \
mutable std::atomic<int> ref_count{0}
int rcp_count() const \
{ \
return rcp_ref_count.load(std::memory_order_relaxed); \
} \
template <typename RCP_T_> friend class rcp; \
rcp_managed(classname)
#define rcp_managed(classname) \
rcp<classname> get_rcp() \
public: \
rcp<classname> get_rcp() const \
{ \
return rcp<classname>(this); \
return rcp<classname>(const_cast<classname *>(this)); \
} \
template <typename... Args> \
static rcp<classname> create(Args&&... args) \
{ \
return (new classname(std::forward<Args>(args)...))->get_rcp(); \
}
} \
private:
-14
View File
@@ -1,14 +0,0 @@
INCLUDE := ../include
.PHONY: all
all: build/tests
build/tests
valgrind --leak-check=full --error-exitcode=1 --log-file=build/valgrind.log build/tests
build/tests: tests.cpp $(INCLUDE)/rcp.h
mkdir -p $$(dirname $@)
$(CXX) -std=c++20 -g -Wall -I$(INCLUDE) -o $@ $<
.PHONY: clean
clean:
rm -rf build
-59
View File
@@ -1,59 +0,0 @@
#include <rcp.h>
#include <cassert>
static int mybase_construct;
static int mybase_destruct;
static int myderived_construct;
static int myderived_destruct;
class MyBase
{
rcp_root();
protected:
MyBase(int x, int y)
{
mybase_construct++;
}
virtual ~MyBase()
{
mybase_destruct++;
}
public:
rcp_managed(MyBase);
};
class MyDerived : public MyBase
{
protected:
MyDerived(double v) : MyBase(1, 2)
{
myderived_construct++;
}
virtual ~MyDerived()
{
myderived_destruct++;
}
public:
rcp_managed(MyDerived)
};
void t1()
{
rcp<MyBase> mybase = MyBase::create(4, 5);
rcp<MyDerived> myderived = MyDerived::create(42.5);
}
int main(int argc, char * argv[])
{
t1();
assert(mybase_construct == 2);
assert(mybase_destruct == 2);
assert(myderived_construct == 1);
assert(myderived_destruct == 1);
return 0;
}
+501
View File
@@ -0,0 +1,501 @@
#include <rcp.h>
#include <cassert>
#include <map>
#include <unordered_map>
#include <vector>
static int mybase_construct;
static int mybase_destruct;
static int myderived_construct;
static int myderived_destruct;
class MyBase
{
rcp_managed_root(MyBase);
protected:
MyBase(int x, int y)
{
this->x = x;
this->y = y;
mybase_construct++;
}
virtual ~MyBase()
{
mybase_destruct++;
}
public:
int x;
int y;
};
typedef rcp<MyBase> MyB;
class MyDerived : public MyBase
{
rcp_managed(MyDerived);
protected:
MyDerived(double v) : MyBase(1, 2)
{
this->v = v;
myderived_construct++;
}
virtual ~MyDerived()
{
myderived_destruct++;
}
public:
double v;
};
void test_class_hierarchy()
{
{
rcp<MyBase> mybase = MyBase::create(4, 5);
rcp<MyDerived> myderived = MyDerived::create(42.5);
}
assert(mybase_construct == 2);
assert(mybase_destruct == 2);
assert(myderived_construct == 1);
assert(myderived_destruct == 1);
}
void test_dereference()
{
rcp<MyBase> mybase = MyBase::create(2, 3);
assert(mybase->x == 2);
assert(mybase->y == 3);
assert((*mybase).x == 2);
assert((*mybase).y == 3);
rcp<MyBase> mybase2 = mybase;
assert(mybase == mybase2);
assert(!(mybase != mybase2));
}
void test_booleans()
{
rcp<MyBase> mybase = MyBase::create(2, 3);
assert(mybase);
assert(mybase != nullptr);
rcp<MyDerived> myderived;
assert(!myderived);
assert(myderived == nullptr);
assert(nullptr != mybase);
assert(nullptr == myderived);
}
void test_create()
{
MyB myb = MyB::create(8, 9);
assert(myb->x == 8);
}
struct MyObj
{
rcp_managed_root(MyObj);
public:
int v = {42};
template <typename R>
void add_to(R & receiver)
{
receiver.add(this);
}
};
struct Receiver
{
std::vector<rcp<MyObj>> objects;
void add(MyObj * o)
{
objects.push_back(rcp<MyObj>(o));
}
};
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()
{
Receiver r;
auto myo = MyObj::create();
for (int i = 0; i < 5; i++)
{
myo->add_to(r);
}
for (int i = 0; i < 5; i++)
{
assert(r.objects[i]->v == 42);
}
}
struct EventListener;
static std::vector<rcp<EventListener>> g_listeners;
struct EventListener
{
rcp_managed_root(EventListener);
public:
int events_received = 0;
void attach() { g_listeners.push_back(this); }
};
void test_listener_self_registration()
{
g_listeners.clear();
auto a = EventListener::create();
auto b = EventListener::create();
a->attach();
b->attach();
for (auto & l : g_listeners) l->events_received++;
assert(a->events_received == 1);
assert(b->events_received == 1);
g_listeners.clear();
}
void test_copy_assignment_decrements_previous_reference()
{
int constructed_before = mybase_construct;
int destructed_before = mybase_destruct;
{
MyB myb = MyB::create(12, 13);
MyB myb2 = MyB::create(14, 15);
myb = myb2;
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()
{
int constructed_before = mybase_construct;
int destructed_before = mybase_destruct;
{
MyB a = MyB::create(1, 2);
MyB b = std::move(a);
assert(!a);
assert(b);
assert(b->x == 1);
assert(mybase_destruct == destructed_before);
}
assert(mybase_construct == constructed_before + 1);
assert(mybase_destruct == destructed_before + 1);
}
void test_move_assignment()
{
int constructed_before = mybase_construct;
int destructed_before = mybase_destruct;
{
MyB a = MyB::create(1, 2);
MyB b;
b = std::move(a);
assert(!a);
assert(b);
assert(b->x == 1);
assert(mybase_destruct == destructed_before);
}
assert(mybase_construct == constructed_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()
{
int constructed_before = mybase_construct;
int destructed_before = mybase_destruct;
{
MyB a = MyB::create(1, 2);
MyB b = MyB::create(3, 4);
b = std::move(a);
assert(!a);
assert(b->x == 1);
assert(mybase_destruct == destructed_before + 1);
}
assert(mybase_construct == constructed_before + 2);
assert(mybase_destruct == destructed_before + 2);
}
void test_cross_type_comparison()
{
rcp<MyDerived> derived = MyDerived::create(1.5);
rcp<MyBase> base = derived;
assert(base == derived);
assert(!(base != derived));
rcp<MyBase> other = MyBase::create(1, 2);
assert(base != other);
assert(!(base == other));
}
void test_reset()
{
int destructed_before = mybase_destruct;
MyB p = MyB::create(1, 2);
p.reset();
assert(!p);
assert(mybase_destruct == destructed_before + 1);
}
void test_upcast()
{
rcp<MyDerived> derived = MyDerived::create(1.5);
rcp<MyBase> base = derived;
assert(base);
assert(base->x == 1);
}
void test_move_upcast()
{
int constructed_before = mybase_construct;
int destructed_before = mybase_destruct;
{
rcp<MyDerived> derived = MyDerived::create(1.5);
rcp<MyBase> base = std::move(derived);
assert(!derived);
assert(base);
assert(base->x == 1);
assert(mybase_destruct == destructed_before);
}
assert(mybase_construct == constructed_before + 1);
assert(mybase_destruct == destructed_before + 1);
}
void test_dynamic_cast_success()
{
rcp<MyDerived> derived = MyDerived::create(1.5);
rcp<MyBase> base = derived;
rcp<MyDerived> back = rcp_dynamic_cast<MyDerived>(base);
assert(back);
assert(back->v == 1.5);
}
void test_dynamic_cast_move_success()
{
int constructed_before = mybase_construct;
int destructed_before = mybase_destruct;
{
rcp<MyBase> base = MyDerived::create(1.5);
rcp<MyDerived> derived = rcp_dynamic_cast<MyDerived>(std::move(base));
assert(!base);
assert(derived);
assert(derived->v == 1.5);
assert(mybase_destruct == destructed_before);
}
assert(mybase_construct == constructed_before + 1);
assert(mybase_destruct == destructed_before + 1);
}
void test_dynamic_cast_move_failure()
{
rcp<MyBase> base = MyBase::create(1, 2);
rcp<MyDerived> derived = rcp_dynamic_cast<MyDerived>(std::move(base));
assert(!derived);
assert(base);
}
void test_dynamic_cast_failure()
{
rcp<MyBase> base = MyBase::create(1, 2);
rcp<MyDerived> derived = rcp_dynamic_cast<MyDerived>(base);
assert(!derived);
}
/* Simulate a external class that cannot be modified */
namespace external
{
class Counter
{
public:
int start;
int count;
Counter(int start) : start(start), count(start) {}
virtual ~Counter() {}
};
}
static int external_destruct;
/* Create a managed wrapper for the external class. */
class Counter : public external::Counter
{
rcp_managed_root(Counter);
protected:
Counter(int start) : external::Counter(start) {}
~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()
{
MyB a = MyB::create(1, 2);
MyB b = MyB::create(3, 4);
a.swap(b);
assert(a->x == 3);
assert(b->x == 1);
std::swap(a, b);
assert(a->x == 1);
assert(b->x == 3);
}
void test_hash()
{
MyB a = MyB::create(1, 2);
MyB b = a;
std::unordered_map<MyB, int> map;
map[a] = 42;
assert(map[b] == 42);
MyB c = MyB::create(3, 4);
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()
{
int before = external_destruct;
{
auto a = Counter::create(10);
auto b = Counter::create(20);
assert(a->count == 10);
assert(b->count == 20);
a->count++;
assert(a->count == 11);
}
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[])
{
test_class_hierarchy();
test_dereference();
test_booleans();
test_create();
test_get_rcp_const();
test_multi_construct_from_raw_pointers();
test_listener_self_registration();
test_copy_assignment_decrements_previous_reference();
test_self_assignment();
test_cross_type_comparison();
test_reset();
test_move_constructor();
test_move_assignment();
test_move_assignment_same_object();
test_move_assignment_releases_existing();
test_upcast();
test_move_upcast();
test_dynamic_cast_success();
test_dynamic_cast_failure();
test_dynamic_cast_move_success();
test_dynamic_cast_move_failure();
test_ordering();
test_use_count();
test_swap();
test_hash();
test_class_template();
test_external_class();
test_assign_nullptr();
return 0;
}