February 26, 2013
Hello, I'm learning D with a strong Delphi/O.Pascal background. As a test I try to implement a system of ownership which completelly bypasses the GC.

For the "uncollected object" ancestor, I use the example provided in the documentation:


class TUObject: Object
{
    new(size_t sz)
    {
        void* p;

        p = std.c.stdlib.malloc(sz);

        if (!p)
            throw new OutOfMemoryError();

        return p;
    }

    delete(void* p)
    {
        if (p)
        {
            std.c.stdlib.free(p);
        }
    }

    unittest
    {
        auto UnmanagedObj = new TUObject;
        assert( GC.getAttr( &UnmanagedObj ) == 0, "UnmanagedObj shouldnt be managed");
        delete UnmanagedObj;
    }
}

Then I have a descendant, it basically illustrates how the owned Objects will be destroyed, even if the ownership system is not at all implemented:

class TOwned: TUObject
{
    private
    {
        TUObject FOwned;
    }

    public
    {
        this()
        {
            FOwned = new TUObject;
        }

        ~this()
        {
            delete FOwned;
            FOwned = null;
        }
    }

    unittest
    {

        TUObject* Owned = null;

        auto Root = new TOwned;

        assert( GC.getAttr( &Root ) == 0, "Root shouldnt be managed");

        Owned = &Root.FOwned;
        delete Root;

        assert(  (*Owned) is null , "dereference of pointer to Root.Owned shoud be null" );
        assert(  (Owned) !is null , "pointer to Root.Owned shoud look valid" );

    }
}

So far everything works fine but I've found that even if I don't delete FOwned in TOwned.~this(), the unittest passes. So now the real problem is illustrated in a similar class:

class TOwnedShouldntPass: TUObject
{
    private
    {
        TUObject FOwned;
    }

    public
    {
        this()
        {
            FOwned = new TUObject;
        }

        ~this()
        {
            // FWoned not free
        }
    }

    unittest
    {

        TUObject* Owned = null;

        auto Root = new TOwnedShouldntPass;

        assert( GC.getAttr( &Root ) == 0, "Root shouldnt be managed");

        Owned =  &Root.FOwned;
        delete Root;

        assert(  (*Owned) !is null , "dereference of pointer to Root.Owned should still be valid" );
        assert(  (Owned) !is null , "pointer to Root.Owned shoud look valid" );
    }
}

The first assertion fails, while I would expect "Owned" to be a dangling pointer. Basically it should not be null but accessing to one of its member would trigger some AV.
Why does the dereference of Owned always equal to null ?
Do I miss a subtility of D pointers/references ?