Thread overview
Reference counting example
Apr 26, 2022
Alain De Vos
Apr 26, 2022
JG
Apr 26, 2022
cc
Apr 26, 2022
cc
Apr 27, 2022
vit
April 26, 2022

Can someone provide a simple/very simple reference counting or refcounted example i can understand. Thanks.

April 26, 2022

On Tuesday, 26 April 2022 at 06:55:34 UTC, Alain De Vos wrote:

>

Can someone provide a simple/very simple reference counting or refcounted example i can understand. Thanks.

I suggest to look at RefCounted here rather than in Phobos. There are simple examples.

April 26, 2022

On Tuesday, 26 April 2022 at 06:55:34 UTC, Alain De Vos wrote:

>

Can someone provide a simple/very simple reference counting or refcounted example i can understand. Thanks.

I've been playing around with the automem[1] library's RefCounted feature as we speak, it seems to fit my needs more than std.typecons which doesn't quite do what I want. I did have to make some changes to the library though to allow for inheritance and manually releasing (below). It's pretty fun so far so I'm looking forward to trying it in some other projects like a non-GC XML library.
[1] https://github.com/atilaneves/automem

Test application:

import std.stdio;
import core.memory;
import util.array; // ARRAY Custom wrapper for std.container.array
// The vector/array library provided with automem does NOT properly destroy array elements
// so we'll use std.container.array instead

import std.experimental.allocator.mallocator;
import automem;

alias RC(T) = RefCounted!(T, Mallocator);
// Optional default constructor workaround
auto RCREATE(T, Args...)(auto ref Args args) {
	return RC!T.create(args);
}

class Farm {
	ARRAY!(RC!Cow) animals;
	//this() {}
	this(int) { writeln("[Farm]"); }
	~this() {
		writeln("[~Farm]");
		animals.clear();
		writeln("[/Farm]");
	}

	void pet(RC!Animal animal) {
		writefln("Farm: The %s says...", animal);
		animal.speak;
	}

}
class Animal {
	void speak() {
		writeln("Animal: ???");
	}
}
class Cow : Animal {
	ARRAY!(RC!Animal) friends; // Amazingly, this works, as long as the array elem type is NOT the same as RC!(this class)
								// otherwise we get a forwarding error
	int x;
	this() { writefln("[Cow]"); }
	this(int x) { this.x = x; writefln("[Cow %s]", x); }
	~this() { writefln("[/Cow %s]", x); }
	override void speak() {
		writefln("Cow#%s: Moo.", x);
	}
}


void main() {
	auto used = GC.stats.usedSize;
	scope(exit) assert(GC.stats.usedSize == used); // GC is not touched!
	{
		assert(RCREATE!Cow.x == 0);
		assert(RCREATE!Cow(99).x == 99);
	}

	RC!Animal other;

	auto farm = RC!Farm(1);
	{
		auto cow = RC!Cow(1);
		farm.animals ~= cow;
		farm.animals ~= RC!Cow(2);
		other = farm.animals[1];
		auto cowGoesOutOfScope = RC!Cow(70);
	}
	writeln("out, should have seen Cow#70's dtor");

	farm.animals[0] = farm.animals[1];
	writeln("animals[0] (Cow#1) just got overwritten so we should have seen its dtor");

	farm.animals ~= RC!Cow(3);

	farm.pet(other);
	other = null;

	farm = null;

	writeln("done");
}

Output:

[Cow]
[/Cow 0]
[Cow 99]
[/Cow 99]
[Farm]
[Cow 1]
[Cow 2]
[Cow 70]
[/Cow 70]
out, should have seen Cow#70's dtor
[/Cow 1]
animals[0] (Cow#1) just got overwritten so we should have seen its dtor
[Cow 3]
Farm: The memtest.Cow says...
Cow#2: Moo.
[~Farm]
[/Cow 2]
[/Cow 3]
[/Farm]
done

I added the following functions to automem ref_counted.d:

    // static .create method to allow use of class's default constructor if desired
    static if (isGlobal && is(Type == class) && __traits(compiles, new Type())) {
        static auto create(Args...)(auto ref Args args) {
            typeof(this) obj;
            obj.makeObject!args();
            return obj;
        }
    }

    // allow instantiation or assignment from derived classes if the Allocator is the same
    this(U)(ref RefCounted!(U,Allocator) rhs) if (is(U == class) && !is(U == Type)) {
        _impl = cast(typeof(_impl)) rhs._impl;
        if(_impl !is null) inc;
    }
    void opAssign(U : Type)(ref RefCounted!(U,Allocator) other) if (is(U == class) && !is(U == Type)) {
//        if (_impl == other._impl) return;
        if (_impl._rawMemory.ptr == other._impl._rawMemory.ptr) return;
        if(_impl !is null) release;
        static if(!isGlobal)
            _allocator = other._allocator;
        _impl = cast(typeof(_impl)) other._impl;
        if(_impl !is null) inc;
    }

    // Allow assigning null to manually release payload
    void opAssign(typeof(null)) {
        if(_impl !is null) release;
        _impl = null;
    }
April 26, 2022

On Tuesday, 26 April 2022 at 22:16:01 UTC, cc wrote:

>

Test application:

I should point out that all this stuff with saving refcounted things to arrays and so on is extremely untested and experimental🙄

One problem I'm seeing is the inability for a refcounted class to pass itself to another function, since the class internals don't see the struct wrapper.. you can pass the naked object reference itself, and hope the reference doesn't get saved otherwise there's your dangling pointer, but then you also have the problem of inconsistent method declarations, with some things taking Foo and others taking RefCounted!Foo etc...

Every night I pray for a refcounted keyword. Wouldn't something like auto foo = new refcount Foo(); be nice? Then every class that deals with the objects could continue to be allocator-agnostic... definitely not a trivial change though.

April 27, 2022

On Tuesday, 26 April 2022 at 23:33:28 UTC, cc wrote:

>

On Tuesday, 26 April 2022 at 22:16:01 UTC, cc wrote:

>

Test application:

I should point out that all this stuff with saving refcounted things to arrays and so on is extremely untested and experimental🙄

One problem I'm seeing is the inability for a refcounted class to pass itself to another function, since the class internals don't see the struct wrapper.. you can pass the naked object reference itself, and hope the reference doesn't get saved otherwise there's your dangling pointer, but then you also have the problem of inconsistent method declarations, with some things taking Foo and others taking RefCounted!Foo etc...

Every night I pray for a refcounted keyword. Wouldn't something like auto foo = new refcount Foo(); be nice? Then every class that deals with the objects could continue to be allocator-agnostic... definitely not a trivial change though.

Yor code has antoher big problem. Class Animal has @safe/pure/nothrow/@nogc destruction but class Cow has @system destructor. When you assign RC!Cow to RC!Animal you can have @safe @nogc ... function call @system destructor of Cow.

I have library with check this kind of error for you (https://code.dlang.org/packages/btl).

If you need aliasing (your case) or weak pointers then try it.


    import std.stdio;
    import core.memory;
    import core.lifetime;

    import btl.autoptr;
    import btl.vector;

    alias ARRAY = Vector;
    alias RC = RcPtr;

    class Animal {
        void speak() {
            writeln("Animal: ???");
        }

        ~this()@system{}   //without this code doesnt compile
    }
    class Cow : Animal {
        ARRAY!(RC!Animal) friends; // Amazingly, this works, as long as the array elem type is NOT the same as RC!(this class)
                                    // otherwise we get a forwarding error
        int x;
        this() { writefln("[Cow]"); }
        this(int x) { this.x = x; writefln("[Cow %s]", x); }
        ~this() { writefln("[/Cow %s]", x); }
        override void speak() {
            writefln("Cow#%s: Moo.", x);
        }
    }

    class Farm {
        ARRAY!(RC!Cow) animals;
        //this() {}
        this(int) { writeln("[Farm]"); }
        ~this() {
            writeln("[~Farm]");
            animals.clear();
            writeln("[/Farm]");
        }

        void pet(RC!Animal animal) {
            writefln("Farm: The %s says...", animal);
            animal.get.speak;
        }

    }

    void main() {
        auto used = GC.stats.usedSize;
        scope(exit) assert(GC.stats.usedSize == used); // GC is not touched!
        {
            assert(RC!Cow.make().get.x == 0);
            assert(RC!Cow.make(99).get.x == 99);
        }

        RC!Animal other;

        auto farm = RC!Farm.make(1);
        {
            auto cow = RC!Cow.make(1);
            farm.get.animals ~= cow;
            farm.get.animals ~= RC!Cow.make(2);
            other = farm.get.animals[1];
            auto cowGoesOutOfScope = RC!Cow.make(70);
        }
        writeln("out, should have seen Cow#70's dtor");

        farm.get.animals[0] = farm.get.animals[1];
        writeln("animals[0] (Cow#1) just got overwritten so we should have seen its dtor");

        farm.get.animals ~= RC!Cow.make(3);

        farm.get.pet(other);
        other = null;

        farm = null;

        writeln("done");
    }