gcroot

A gcroot represents a smart pointer that retains shared protection of a heap allocated IObject which is registered in a CSpace.

Unlike a std::shared_ptr<T> where T is the element type, a gcroot<P> is parameterised in the pointer type P. For example the pointer type can be a normal memory pointer, or an interface pointer:

The shared protection of the object means it is protected from being destroyed by the CSpace tracing garbage collector.

We call it a "gc root" because it represents a root object for the tracing garbage collector. In other words, the gcroot smart pointer not only protects the referenced object, it also protects all objects which are reachable from that object.

A gcroot may also own no objects, in which case it is called empty. This is the case for a default constructed gcroot. A gcroot can be made empty by calling reset() with no arguments, by calling reset(nullptr) or by assigning with nullptr.

gcroot variables may for example be:

In the following example there is a gcroot variable named p declared on the frame. Variable p protects the object it references during its lifetime. In particular, the call to the method foo() is made without fear of the object being destroyed, even though the CSpace is no longer locked when foo() is called.


gcroot<X*> p;
{
    CSpaceLock lock;
    p.reset($new X(2.3, false, "hello world"));
}
p->foo();

Shared protection is not shared ownership

The shared protection of an object using gcroot is analogous to the shared ownership using std::shared_ptr<T> except a gcroot is used to stop an object from being garbage collected whereas a std::shared_ptr is used to ensure an object is deleted.

Indeed the following code creates a memory leak, because the object instance has been allocated with new instead of $new and therefore has not been registered in a CSpace. The gcroot protects the object from being garbage collected but it isn't going to be garbage collected anyway.


gcroot<X*> p(new X);

Copy construction and copy assignment

If pointer type From is convertible to pointer type To (i.e. std::is_convertible<From,To>::value is true) then gcroot<To> supports copy construction and copy assignment from gcroot<From>. This creates multiple pointers to the same object, each of them protecting the shared object from being garbage collected.

Independence from CSpace locks

gcroot instances can be freely constructed, destructed, copy constructed and copy assigned without regard for CSpace locks. These operations don't acquire CSpace locks and they don't assume the calling thread either has or doesn't have a CSpace lock.

Thread safety

All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of gcroot without additional synchronization even if these instances are copies and share ownership of the same object.

If multiple threads of execution access the same gcroot without synchronization and any of those accesses uses a non-const member function of gcroot then a data race will occur.

Unreferenced objects inside a CSpace lock

An object that is or becomes unreferenced by any gcroot inside a CSpaceLock is nevertheless protected from garbage collection during the CSpaceLock.

For example, in the following the object is temporarily unprotected yet it cannot be garbage collected:


void f(gcroot<X*> r)
{
    {
        CSpaceLock lock;
        X* p = r.get();
        r.reset();      // Unprotect the object
        r.reset(p);     // Protect it again
    }
    r->foo();           // Safe
}

Indeed this is the state of affairs for the object returned by invoking $new. At that point in time the object is unreferenced, but is nevertheless protected from garbage collection during the CSpace lock.

If an object becomes referenced by a gcroot before the CSpace lock is released then that guarantees the object will not be garbage collected.

No assignment from raw pointer

Note that the following is not assignment, it is copy initialization:


gcroot<X*> p = $new X;       // compiler error

It doesn't work because the constructor of gcroot taking a pointer type is marked as explicit:


template<typename T>
class gcroot
{
public:
    template <class R> explicit gcroot(R p) noexcept;
    ...
};

Assignment from a raw pointer doesn't work either, because gcroot doesn't have an overloaded assignment operator taking a raw pointer as parameter.


gcroot<X*> p;
p = $new X;       // compiler error

Instead, the overload of reset taking a raw pointer can be called on a gcroot to make it take ownership of an existing object.

make_gcroot

The free function make_gcroot is provided for convenience in allocating a object, registering it in the CSpace and returning a pointer to it using a gcroot.


template <class T, class... Args>
gcroot<T*> make_gcroot(Args&&... args);

The following illustrates how it can be used:


gcroot<X*> p;
{
    CSpaceLock lock;
    p = make_gcroot<X>(2.3, false, "hello world");
}
p->foo();

make_gcroot<T>(...) is like $new T(...) in that:

However make_gcroot<T>(...) returns a gcroot<T*> whereas $new T(...) returns a raw T*.

make_gcroot<T>(...) perfect forwards the arguments onto the T constructor in a similar way to std::make_shared.