Compare commits
1 Commits
main
...
readme-wip
| Author | SHA1 | Date | |
|---|---|---|---|
| c3915cc8af |
298
README.md
298
README.md
@ -1,3 +1,299 @@
|
||||
# rcp
|
||||
|
||||
`rcp` is a reference-counting pointer implementation for C++.
|
||||
`rcp` is an intrusive reference-counting smart pointer for C++11(+).
|
||||
The reference count is stored inside the managed object itself, eliminating
|
||||
the separate heap allocation that non-intrusive implementations require.
|
||||
Memory is always freed when the last `rcp` to an object is destroyed.
|
||||
|
||||
## Usage
|
||||
|
||||
Add `rcp_managed_root` to the root of your class hierarchy.
|
||||
For derived classes, add `rcp_managed`.
|
||||
|
||||
```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"; }
|
||||
};
|
||||
```
|
||||
|
||||
`rcp_managed_root` injects the reference count and the increment/decrement
|
||||
methods.
|
||||
`rcp_managed` injects `create()` and `get_rcp()` into derived classes.
|
||||
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
|
||||
|
||||
Use the static `create()` method, which returns an `rcp` holding the newly
|
||||
constructed object:
|
||||
|
||||
```cpp
|
||||
rcp<Animal> animal = Animal::create("Cat");
|
||||
rcp<Dog> dog = Dog::create("Rex");
|
||||
```
|
||||
|
||||
`create()` forwards its arguments to the class constructor.
|
||||
Constructors should be `protected` or `private` to ensure objects are
|
||||
always managed by `rcp`.
|
||||
|
||||
## Wrapping external classes
|
||||
|
||||
`rcp` can add intrusive reference counting to a class you don't own and
|
||||
cannot modify.
|
||||
Derive from the external class, add `rcp_managed_root` to the derived
|
||||
class, and give the derived class the same name inside your own namespace.
|
||||
Consumers use your type and never interact with the external class
|
||||
directly.
|
||||
|
||||
```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.
|
||||
The virtual destructor on the external class ensures that the derived
|
||||
destructor is called correctly when the last `rcp` is released.
|
||||
|
||||
## 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";
|
||||
|
||||
dog.reset(); // releases the reference; object freed if this was the last holder
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Copying and moving
|
||||
|
||||
Copying an `rcp` increments the reference count.
|
||||
Moving 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 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`, constructing from a raw `this` pointer creates an
|
||||
independent control block, leading to a double-free when both reach zero.
|
||||
|
||||
```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` |
|
||||
|---|---|---|
|
||||
| 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` | `get_rcp()` | `std::enable_shared_from_this` |
|
||||
| Checked downcast | `rcp_dynamic_cast` | `std::dynamic_pointer_cast` |
|
||||
|
||||
The intrusive design means each managed object requires only one heap
|
||||
allocation instead of two.
|
||||
The trade-off is that the class must be written with `rcp` in mind.
|
||||
|
||||
`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` is more flexible — 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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user