README.md WIP
This commit is contained in:
parent
d2b6755704
commit
1c8f93983c
298
README.md
298
README.md
@ -1,3 +1,299 @@
|
|||||||
# rcp
|
# 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.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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