Index
x::const_ref
and x::const_ptr
create
() - create reference-counted objectsbase
x::ref
or an x::ptr
from this
x::ref
s and x::ptr
sisa
()
Nearly all objects in LibCXX are reference-counted objects,
similar to what std::shared_ptr
implements, but with notable differences as described here.
In this context, this is not the same kind of a “reference”
that's a part of the C++ language itself.
For clarity, “native references” will refer to traditional
C++ references, while the general usage of the term “reference
pointer”, or a
“reference”, refers to the construct described here.
Reference-counted objects do not get
accessed with an ordinary pointer, but by using a reference
pointer, an x::ref
or an x::ptr
.
A reference-counted object gets instantiated in dynamic scope using
a variadic create
() function that forwards its
arguments to the object's constructor;
create
()
instantiates the object in dynamic scope and
creates the first, initial reference pointer to the
newly-instantiated object.
Reference pointers look like ordinary pointers. They have
*
and ->
operators with
the expected behavior. Reference pointers may be freely copied and
passed around.
create()
replaces new
.
new
does not get used to construct reference-
counter object in dynamic scope, and must not be used.
Similarly, delete
does not get used, explicitly, with
reference-counted objects.
When the last reference to an object goes out of scope and longer exists,
the last reference pointer takes care of destroying the object
with a delete
, and invoking any
destructor callbacks.
pthread_cancel
(3) cannot be used in code that uses
LibCXX. pthread_cancel
(3) terminates a thread without
unwinding the stack. The stack may contain references to
reference-counted objects, or other objects
whose destructors contain critical functionality.
Using pthread_cancel
(3) will result in memory leaks,
deadlocks, and other unstable behavior. This is likely to be the case with
any C++-based process of non-trivial complexity,
not just LibCXX. The only safe way to forcibly terminate the thread is
by throwing an exception that unwinds the entire stack frame.
x::msgdispatcherObj
provides a convenient message-based thread design pattern which
supplies a stop
() method that sends a message to the
running thread that causes it to throw an exception and terminate, in an
orderly manner.
This implementation of reference-counted objects is
similar to other similar implementations, notably
shared_ptr
in the C++ library, however there are
fundamental differences. The key differences are:
A shared_ptr
contains a pointer to a small heap-allocated object
that tracks the reference count, and a second pointer to the
actual object:
LibCXX's reference-counted objects use a different approach
that's similar to Java's implementation of managed objects.
LibCXX's reference-counted objects are derived from the
x::obj
superclass, which tracks
the reference
count.
There are two main reference pointer classes,
x::ref
and x::ptr
, holding a single pointer
to the object:
shared_ptr
's
*
and
->
operators
dereference a mutable object,
and dereferencing a nullptr
is
undefined behavior.
x::ptr
can also be a nullptr
, but dereferencing a
nullptr
is not undefined behavior:
an exception gets thrown, with defined semantics.
x::ref
's *
and ->
operators do not check for a nullptr
because x::ref
cannot be a nullptr
.
It always
refers to an object. This is enforced by contract.
The nullptr
gets checked when an x::ref
gets assigned to.
x::ref
is proven, by contract, never to contain a
nullptr
; hence its
*
and ->
directly
dereferences the internal pointer to the underlying object.
An x::ref
lvalue is convertible to an x::ptr
lvalue without a temporary.
Converting an x::ptr
to an x::ref
checks for a
nullptr
at the time the conversion takes place, where an exception gets
thrown. An x::ptr
may be passed to a function that takes an x::ref
argument. If the caller supplies a nullptr
,
the exception gets thrown in the caller's context, for violating
the contract. The backtrace accurately points to the guilty party,
not the function, but its caller.
x::ptr
and x::ref
are analogous to natural C++ pointers and
references. Similarly, LibCXX defines an x::const_ptr
and an
x::const_ref
, that dereference to constant objects. This is
directly analogous to a std::iterator
and a
std::const_iterator
. Nothing similar is
available with a shared_ptr
.
A shared_ptr<T>
is convertible to a
shared_ptr<const T>
, but doing that
for a function call requires the construction of a temporary.
An x::ptr
lvalue converts to an x::const_ptr
lvalue without a temporary,
ditto for x::ref
and an x::const_ref
.
The primary benefit of using shared_ptr
is that it allows any class to acquire reference-counting
semantics without modification. Standard C++ library classes, such as
various stream objects, can be easily wrapped into a
reference-counted framework.
However, there are several disadvantages to
shared_ptr
that x::ref
and x::ptr
aim to address.
Each reference-counted object requires allocating another small
object, the reference counter, from the heap. Over the long term,
this is more likely to increase
heap fragmentation, and heap usage, especially
in long running applications, as reference-counted objects get
created and destroyed. make_shared
partially mitigates this, however the end result has to be
compatible with the stock shared_ptr
and that
introduces additional behind the scenes complexity.
There is no straightforward way to prove, by contract, that a
shared_ptr
instance is not a
nullptr
.
It's not possible to construct a shared_ptr
from this
, unless the class explicitly
inherits from an enable_shared_from_this
superclass. But this cancels out
shared_ptr
primary benefit of wrapping any
arbitrary class. Furthermore, this approach becomes somewhat
difficult when multiple inheritance gets involved.
LibCXX stores the reference count in each object's superclass,
x::obj
which is
directly accessible by any subclass. A new x::ref
or an x::ptr
gets trivially constructed from this
,
without much fanfare.
This creates a new reference, and increments
the object's reference count.
Furthermore, since the counter is a part of the object, it does not
need to be allocated separately, on the heap. The only drawback is that,
unlike with shared_ptr
, it's not possible to
implement reference-counted semantics for an arbitrary class. It is
necessary to declare a subclass that multiply
inherits from that class, and
from x::obj
.
x::ref
s and x::ptr
s work with multiple inheritance without any special
effort. Each reference-counted class
virtually inherits from x::obj
, which
automatically does the right thing when multiple reference-counted
classes get inherited from.