Two templates,
x::ptr
and
x::ref
keep track of reference-counted objects:
#include <x/ptr.H> typedef x::ptr<buttonObj> buttonptr; buttonptr b=buttonptr::create(200, 300); // ... b->click(); // Alternatively: #include <x/ref.H> typedef x::ref<buttonObj> button; button b=button::create(200, 300);
As previously mentioned, reference-counted objects get allocated in
dynamic scope, but not by new
.
This is done by the overloaded create
()
function.
The description of this variadic method,
a member of both
x::ref
and x::ptr
,
follows later.
The variadic create
() forwards its parameters
to the constructor for the
reference-counted class, that gets instantiated in dynamic scope.
In this example,
when b
, and all other references go out of scope, the
underlying object gets destroyed. An explicit
delete
never gets used after constructing
a reference-counted object. It gets invoked automatically, by the last
x::ref
or an x::ptr
when it goes out of scope.
The only difference between an x::ptr
and an
x::ref
is that an x::ref
always
refers to an object (and therefore does not have a default constructor,
since it must be constructed with some object, usually a
create
() one, at hand.
x::ptr
, however, does not have to refer to an instance of a
reference-counted class. This is logically
equivalent to a nullptr
:
typedef x::ptr<buttonObj> buttonptr; buttonptr b; // ... if (!b.null()) b->method(); // Or simply: if (!b) b->method();
An x::ref
does not have a
null
(), because it always refers to some object.
An x::ref
is convertible to an
x::ptr
to the same class.
An x::ptr
is also convertible to an
x::ref
, but an
exception gets thrown at the point the
conversion is attempted if the x::ptr
is unbound.
It follows that an x::ref
does not have
a default constructor. All x::ref
s
must be constructed to refer to some existing instance of the
referenced class.
class buttonObj { public: buttonObj(int width, int height); // ... void click(); }; typedef x::ref<buttonObj> button; button b(button::create(400, 200)); // ... b->click();
Calling create
() is just like calling
new
,
where classname
classname
is the referenced object.
This creates a new object using the arguments passed to
create
().
With no arguments, the default constructor gets used. Passing
additional arguments to create
() invokes
the appropriate constructor for the new object.
Each individual x::ref
or an x::ptr
can only be accessed by one thread, at a time,
but different threads may have their own x::ref
s and x::ptr
s referencing
the same object, and use their own x::ref
s and x::ptr
s, without
coordinating with other threads. Reference counting by different
x::ref
s and x::ptr
s is thread-safe.
The object gets destroyed
when all x::ref
s and x::ptr
s to the object, in all threads, go out of
scope.
The object's destructor gets invoked by the thread that held the
last x::ref
or an x::ptr
to the object, which may not necessarily be the same
thread that constructed the object.
Whichever lucky winner let the last x::ref
or an x::ptr
go out of scope gets
to destroy the object, and invoke its
destructor callbacks.
In this context, thread safety also
does not mean that all classes derived from
x::obj
are automatically thread safe, just that their reference-counting
is thread safe.
Classes used by multiple threads are responsible for
providing thread-safe class method implementations.
As mentioned previously,
unlike an x::ref
, an x::ptr
may not be bound to any actual
object:
typedef x::ptr<buttonObj> buttonptr; buttonptr b; // ... if (!b) // Or if (b.null()), to be more verbose. { // ... }
Here, b
gets instantiated without referring to any
object. Using *
and ->
on an
unbound x::ptr
throws an exception, until the x::ptr
is assigned
to an actual object.
null
() returns true when the x::ptr
is
not bound to any object. An x::ptr
in boolean context is equivalent to
!null()
.