Compare commits
37
Commits
eaad6ba6e0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b695ce7758 | ||
|
|
e3cdfb1bfa | ||
|
|
339f1ce9d6 | ||
|
|
771473b62a | ||
|
|
3a624ee65b | ||
|
|
27420c507d | ||
|
|
93527f8647 | ||
|
|
7d5316bd79 | ||
|
|
0de327d442 | ||
|
|
8d0243dcde | ||
|
|
b87fbc0d76 | ||
|
|
d88922756d | ||
|
|
ec937b2395 | ||
|
|
17a647b897 | ||
|
|
d9a9a88a24 | ||
|
|
af0e0d0084 | ||
|
|
d2b6755704 | ||
|
|
464527a34d | ||
|
|
011460254d | ||
|
|
403db54664 | ||
|
|
627661158e | ||
|
|
69ecab39f2 | ||
|
|
11555ded36 | ||
|
|
ddaae32525 | ||
|
|
7880adef89 | ||
|
|
ce921e31be | ||
|
|
174a45cfe7 | ||
|
|
62b729badd | ||
|
|
2e30a9f407 | ||
|
|
4a1a0c8855 | ||
|
|
47273edf6e | ||
|
|
21f2dd4cad | ||
|
|
ffe8433d55 | ||
|
|
be2da870bb | ||
|
|
9dfb6e4740 | ||
|
|
53b2247432 | ||
|
|
83ca3d8893 |
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
/test/build/
|
||||
/build/
|
||||
@@ -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
|
||||
@@ -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
@@ -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:
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user