digitalmars.D.learn - Reference counting example
- Alain De Vos (2/2) Apr 25 2022 Can someone provide a simple/very simple reference counting or
Can someone provide a simple/very simple reference counting or refcounted example i can understand. Thanks.
Apr 25 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](https://code.dlang.org/packages/automem) rather than in Phobos. There are simple examples.
Apr 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: ```d 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() { } } 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); } farm.animals[0] = farm.animals[1]; 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] [/Cow 1] its dtor [Cow 3] Farm: The memtest.Cow says... [~Farm] [/Cow 2] [/Cow 3] [/Farm] done ``` I added the following functions to automem `ref_counted.d`: ```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; } ```
Apr 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.
Apr 26 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: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. ```d 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() { } } 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); } farm.get.animals[0] = farm.get.animals[1]; should have seen its dtor"); farm.get.animals ~= RC!Cow.make(3); farm.get.pet(other); other = null; farm = null; writeln("done"); } ```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.
Apr 26 2022