Add README contents
This commit is contained in:
parent
d2b6755704
commit
af0e0d0084
367
README.md
367
README.md
@ -1,3 +1,368 @@
|
|||||||
# rcp
|
# 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` |
|
||||||
|
|---|---|---|
|
||||||
|
| 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` |
|
||||||
|
|
||||||
|
The intrusive design 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.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user