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 on the heap, but not by using 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 on the heap. 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, and may be undefined, this is analogous to a nullptr:

typedef x::ptr<buttonObj> buttonptr;

buttonptr b;

// ...

if (!b.null())
   b->method();

An x::ref does not have a null(), because it always refers to some object. An x::ref can always be converted to an x::ptr to the same class. An x::ptr can also be converted to a x::ref, but if its unbound, an exception gets thrown at the point the conversion is attempted.

It follows that a x::ref does not have a default constructor. All x::refs must be constructed to refer to some existing instance of the referenced class.

An x::ref or an x::ptr to one class is convertible to an x::ref or an x::ptr to another class, if the classes are related. A dynamic_cast converts the underlying pointer. An exception gets thrown if the dynamic_cast fails.

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 separate 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 a x::ref, a x::ptr may not be bound to any actual object:

typedef x::ptr<buttonObj> button;

button b;

// ...

if (b.null())
{

    // ...
}

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.