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::refs
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 classnameclassname 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::refs and x::ptrs referencing
the same object, and use their own x::refs and x::ptrs, without
coordinating with other threads. Reference counting by different
x::refs and x::ptrs is thread-safe.
The object gets destroyed
when all x::refs and x::ptrs 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().