Creating an x::ref or an x::ptr from this

An x::ref or an x::ptr can be constructed from a native object pointer. This is frequently used by class methods to create pointers or references to the this object:

class widgetObj : virtual public x::obj {

// ...

};

typedef x::ref<widgetObj> widget;

// ...

void widgetObj::method()
{
    // ...

    widget myRef(this);

    // ...
}

This allows a class method to create an x::ref or an x::ptr to the object pointed by this, so that it can be passed along to other objects or methods, for example. Note, however:

Note

Creating an x::ref or an x::ptr from this in the object's constructor abort()s. Consider the following scenario:

  • create() creates the object. The new object's reference count does not get incremented from its initial value of zero, to one, until the object gets fully constructed.

  • With the initial reference count still zero, one the constructors makes an explicit x::ref<classname>(this) creating a new x::ref. This increments the object's reference count to one.

  • This x::ref goes out of scope and gets destroyed, also before the constructor finishes. This now decrements the reference count back to zero. When an object's reference count goes to 0, it gets destroyed. This is now happening before the object is fully constructed. Even if that, by some miracle of miracles, doesn't blow up, the remaining constructors continue to execute, and eventually the original x::ref attempts to increment what it thinks is the initial reference count, in the object that's already destroyed.

This is undefined behavior. The same holds true with creating an x::ref to an object its destructor. The object's reference count is zero, and it's getting destroyed no matter what. A new x::ref (or an x::ptr, doesn't matter) to the object does not prevent that from happening. The end result is an x::ref or an x::ptr to a destroyed object.

For this reason, attempting to create an x::ref or an x::ptr to this in its constructor or destructor immediately abort()s, to aid in debugging. It's true that it's technically possible to have everything be legal with a x::ref or an x::ptr from this created in its constructor, as long as the object succesfully finishes its full construction. However, LibCXX takes the pessimistic view that this can't be guaranteed (on the assumption that an exception can get thrown at any time), and prevents this from happening. Constructing an x::ref or an x::ptr from this in the constructor is a bad idea.

Technical note: it's not really true that the a newly-constructed object starts with a reference count of 0. Internally, LibCXX sets the initial reference count to -1, and resets it to 0 just before create() creates and returns the first x::ref or an x::ptr, which immediately updates it to 1. The reference count of -1 gets used to detect an attempt to create an x::ref or an x::ptr in a constructor (or the destructor).

For this reason, new reference-counted objects must always get created with create() in order to properly initialize the new object's reference count. Manually instantiating the object with new, and assigning it to an x::ref or an x::ptr, also aborts the program.

Second phase constructor

constructor() is an ordinary class method, but if it exists create() calls it automatically after constructing a new object, but before returning the new x::ref or the new x::ptr. This is a convenient workaround for not being able to create an x::ref or an x::ptr in a real constructor. A class with a constructor() method must inherit from x::with_constructorObj in addition to x::obj

class buttonObj : x::with_constructorObj,
                  virtual public x::obj {

public:
    buttonObj(int width, int height);

    void constructor(int width, int height);
};

auto b=x::ref<buttonObj>::create(100, 200);

create() forwards its parameters to the newly-constructed object normally. Afterwards, create() calls the constructor() method with the same parameters that were forwarded to create(). The constructor() method must be callable with the same parameters and should return void.

When constructor() gets invoked its object is fully constructed, for the purpose of the C++ language itself, and for the purpose of using x::refs and x::ptrs. The ban on creating x::refs and x::ptrs from this in constructors do not apply to the constructor() method itseld. It's just an ordinary class method.

A constructor() may be viewed as a second phase of constructing an object, and may be utilized for other reasons besides the ability to create x::refs and x::ptrs from this. If the class's contract requires some kind of an initialization method that always gets invoked after its construction, constructor() is this method. This becomes a part of the class's contract.

x::with_constructorObj should not be virtually-inherited.

Using deduction guides with an x::ref or an x::ptr

class buttonObj : virtual public x::obj {

// ...

     void clicked();
};

void buttonObj::clicked()
{
    auto r=x::ref(this);

};

A natural deduction guide for an x::ref or an x::ptr (also x::const_ref and x::const_ptr too) simplifies creating an x::ref or an x::ptr from this (or from some other native pointer). Note that this results in the default base typedef of x::ptrref_base, for the constructed reference or a pointer. However the deduction guide uses the x::base_type specialization, if one is available:

class buttonObj : virtual public x::obj {

// ...

     void clicked();
};

template<>
struct x::base_type<buttonObj> {

    typedef buttonBase type;
};

void buttonObj::clicked()
{
    auto r=x::ref(this);

};

Because this specialization overrides the second template parameter to an x::ref or an x::ptr, this constructs a x::ref<buttonObj, buttonBase>.