diff --git a/README.md b/README.md index 16d13b5..824c1e5 100644 --- a/README.md +++ b/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::create("Cat"); +rcp 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::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` 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 pixels; +}; +typedef rcp 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 a = Dog::create("Rex"); +rcp b = a; // a and b share ownership; refcount = 2 +rcp c = std::move(a); // c takes ownership from a; a is now null; refcount = 2 +``` + +## Upcasting + +Assigning an `rcp` to an `rcp` is implicit. +Attempting a downcast this way is a compile error: + +```cpp +rcp dog = Dog::create("Rex"); +rcp animal = dog; // ok: implicit upcast +rcp 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 = Dog::create("Rex"); + +// copy downcast: animal remains valid regardless of outcome +rcp dog = rcp_dynamic_cast(animal); + +// move downcast: animal is nulled on success, left intact on failure +rcp dog2 = rcp_dynamic_cast(std::move(animal)); +``` + +## Comparison + +```cpp +rcp a = Dog::create("Rex"); +rcp b = a; +rcp 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 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> 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 self() +{ + return std::dynamic_pointer_cast(shared_from_this()); +} +``` + +With `rcp`, `rcp_managed` injects a `get_rcp()` that returns the correct +derived type directly: + +```cpp +// rcp: returns rcp directly, no cast needed +rcp 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.