Pointers and references

Two templates, x::ptr and x::ref keep track of reference-counted objects:

#include <x/ptr.H>

typedef x::ptr<buttonObj> button;

button b(button::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 classname, where 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::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.

Note

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> button;

button 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().