IAllocator
interface, which we will use, allows us to create and destroy class instances correctly with a single command (correctly, calling all constructors and all destructors of the class hierarchy), but the functions that do this are not marked as @nogc and not to inflate the article implement them independently (partly in the last article it was considered). static this() { RC.affixObj = allocatorObject(RC.affix); } // struct RC { static: alias affix = AffixAllocator!(Mallocator,size_t,size_t).instance; // IAllocator affixObj; // auto make(T,A...)( auto ref A args ) { return incRef( affixObj.make!T(args) ); } // , private void dispose(T)( T* p ) { affixObj.dispose(p); } private void dispose(T)( T p ) if( is(T == class) || is(T == interface) ) { affixObj.dispose(p); } // affix.prefix( void[] p ), p -- , // , ref size_t refCount(T)( T p ) if( is(T == class) || is(T == interface) ) { return affix.prefix( (cast(void*)p)[0..__traits(classInstanceSize,T)] ); } ref size_t refCount(T)( T* p ) { return affix.prefix( (cast(void*)p)[0..T.sizeof] ); } // , auto incRef(T)( auto ref T p ) { if( p is null ) return null; refCount(p)++; return p; } // , , null 0 auto decRef(T)( T p ) { if( p is null ) return null; if( refCount(p) > 0 ) { refCount(p)--; if( refCount(p) == 0 ) { dispose(p); return null; } } return p; } }
Mallocator
, an allocator that works through C-shny malloc
and free
. We wrapped it in AffixAllocator
. It is parameterized by the present real allocator and 2 data types. When allocating AffixAllocator
allocate a little more: the size of the Prefix
and Suffix
types (respectively, the second and third template parameters). As you can guess, the prefix is ​​located before the memory allocated for the object, and the suffix after. In our case, Prefix
and Suffix
are size_t
, and just the prefix will impersonate the reference count. auto p = RC.make!int( 10 ); assert( is( typeof(p) == int* ) ); assert( *p == 10 ); assert( RC.refCount(p) == 1 ); p = RC.decRef(p); assert( p is null );
struct RCObject(T) { T obj; alias obj this; // , obj RCObject this(this) { incRef(); } // -- this( T o ) { obj = o; incRef(); } // -- // ( RC.make) package static auto make( T o ) { RCObject!T ret; ret.obj = o; return ret; } // nothrow std.experimental.allocator ~this() nothrow { if( obj is null ) return; try decRef(); catch(Exception e) { import std.experimental.logger; try errorf( "ERROR: ", e ); catch(Exception e) {} } } void incRef() { if( obj is null ) return; RC.incRef(obj); } /// obj void decRef() { if( obj is null ) return; assert( refCount > 0, "not null object have 0 refs" ); obj = RC.decRef(obj); } size_t refCount() @property const { if( obj is null ) return 0; return RC.refCount(obj); } // , void opAssign(X=this)( auto ref RCObject!T r ) { decRef(); obj = r.obj; incRef(); } /// T, void opAssign(X=this)( auto ref T r ) { decRef(); obj = r; incRef(); } } // auto rcMake(T,A...)( A args ){ return RCObject!(T).make( RC.make!T(args) ); }
static class Ch { } { RCObject!Ch c; { auto a = rcMake!Ch(); assert( a.refCount == 1 ); auto b = a; assert( a.refCount == 2 ); assert( b.refCount == 2 ); c = a; assert( a.refCount == 3 ); assert( b.refCount == 3 ); assert( c.refCount == 3 ); b = rcMake!Ch(); assert( a.refCount == 2 ); assert( b.refCount == 1 ); assert( c.refCount == 2 ); b = rcMake!Ch(); // , assert( a.refCount == 2 ); assert( b.refCount == 1 ); assert( c.refCount == 2 ); } assert( c.refCount == 1 ); }
... T[] makeArray(T,A...)( size_t length ) { return incRef( affixObj.makeArray!T(length) ); } T[] makeArray(T,A...)( size_t length, auto ref T init ) { return incRef( affixObj.makeArray!T(length,init) ); } private void dispose(T)( T[] arr ) { affixObj.dispose(arr); } ref size_t refCount(T)( T[] arr ) { return affix.prefix( cast(void[])arr ); } ...
struct RCArray(T) { // , private T[] orig; // T[] work; private void init( T[] origin, T[] slice ) { if( slice !is null ) assert( slice.ptr >= origin.ptr && slice.ptr < origin.ptr + origin.length, "slice is not in original" ); orig = origin; incRef(); work = slice is null ? orig : slice; static if( isRCType!T ) // foreach( ref w; work ) w.incRef; // } /// alias work this; this(this) { incRef(); } this( T[] orig, T[] slice=null ) { init( orig, slice ); } /// no increment ref package static auto make( T[] o ) { RCArray!T ret; ret.orig = o; ret.work = o; return ret; } // auto opSlice( size_t i, size_t j ) { return RCArray!T( orig, work[i..j] ); } void opAssign(X=this)( auto ref RCArray!T arr ) { decRef(); init( arr.orig, arr.work ); } void incRef() { if( orig is null ) return; RC.incRef(orig); } void decRef() { if( orig is null ) return; assert( refCount > 0, "not null object have 0 refs" ); orig = RC.decRef(orig); } size_t refCount() @property const { if( orig is null ) return 0; return RC.refCount(orig); } ~this() { if( refCount ) { // if( refCount == 1 ) { static if( isRCType!T ) foreach( ref w; orig ) if( w.refCount ) w.incRef; } static if( isRCType!T ) // foreach( ref w; work ) w.decRef; decRef; } } } template isRCType(T) { static if( is( TE == RCObject!X, X ) || is( TE == RCArray!X, X ) ) enum isRCType = true; else enum isRCType = false; }
orig
is 1, which means that if the serse is also deleted, then dispose
will be called for the original ( orig
) array, this will result in objects taken from the original array but not falling into will reduce the number of references and can be deleted. To prevent this from happening, we add +1 to each element that already has more than 1. Then there will be a decrease in the working set, this will allow to leave 1 more for the elements of the original array that were not included in the working set and when the original array was deleted, their counter will not reach zero. class A { int no; this( int i ) { no=i; } } alias RCA = RCObject!A; { RCA obj; { RCArray!RCA tmp1; { RCArray!RCA tmp2; { auto arr = rcMakeArray!RCA(6); foreach( int i, ref a; arr ) a = rcMake!A(i); assert( arr[0].refCount == 1 ); assert( arr[1].refCount == 1 ); assert( arr[2].refCount == 1 ); assert( arr[3].refCount == 1 ); assert( arr[4].refCount == 1 ); assert( arr[5].refCount == 1 ); tmp1 = arr[1..4]; assert( arr[0].refCount == 1 ); assert( arr[1].refCount == 2 ); assert( arr[2].refCount == 2 ); assert( arr[3].refCount == 2 ); assert( arr[4].refCount == 1 ); assert( arr[5].refCount == 1 ); tmp2 = arr[3..5]; assert( arr[0].refCount == 1 ); assert( arr[1].refCount == 2 ); assert( arr[2].refCount == 2 ); assert( arr[3].refCount == 3 ); assert( arr[4].refCount == 2 ); assert( arr[5].refCount == 1 ); obj = tmp2[0]; assert( arr[0].refCount == 1 ); assert( arr[1].refCount == 2 ); assert( arr[2].refCount == 2 ); assert( arr[3].refCount == 4 ); assert( arr[4].refCount == 2 ); assert( arr[5].refCount == 1 ); } assert( tmp1[0].refCount == 1 ); assert( tmp1[1].refCount == 1 ); assert( tmp1[2].refCount == 3 ); assert( obj.refCount == 3 ); assert( tmp2[0].refCount == 3 ); assert( tmp2[0].obj.no == 3 ); assert( tmp2[1].refCount == 1 ); assert( tmp2[1].obj.no == 4 ); } assert( tmp1[0].refCount == 1 ); assert( tmp1[1].refCount == 1 ); assert( tmp1[2].refCount == 2 ); assert( obj.refCount == 2 ); } assert( obj.refCount == 1 ); } // 3
@nogc
, it does not refer to the embedded GC. And as we know, Source: https://habr.com/ru/post/304074/
All Articles