1 /** 2 * Basic reference-counting for classes. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.utils.meta.rcclass; 15 16 import core.memory; 17 18 import std.conv : emplace; 19 20 private struct RCClassStore(C) 21 { 22 size_t refCount = void; 23 void[__traits(classInstanceSize, C)] data = void; 24 } 25 26 /// Wraps class type `C` into a reference-counting value type. 27 struct RCClass(C) 28 if (is(C == class)) 29 { 30 // storage 31 32 private RCClassStore!C* _rcClassStore; 33 34 @property inout(C) _rcClassGet() inout 35 { 36 return cast(C)_rcClassStore.data.ptr; 37 } 38 39 alias _rcClassGet this; 40 41 // construction 42 43 this(T)(T value) 44 if (is(T == RCClass!U, U) && is(typeof({U u; C c = u;}))) 45 { 46 _rcClassStore = cast(RCClassStore!C*)value._rcClassStore; 47 if (_rcClassStore) 48 _rcClassStore.refCount++; 49 } 50 51 // operations 52 53 ref typeof(this) opAssign(T)(T value) 54 if (is(T == typeof(null))) 55 { 56 assert(value is null); 57 _rcClassDestroy(); 58 _rcClassStore = null; 59 return this; 60 } /// 61 62 ref typeof(this) opAssign(T)(auto ref T value) 63 if (is(T == RCClass!U, U) && is(typeof({U u; C c = u;}))) 64 { 65 _rcClassDestroy(); 66 _rcClassStore = cast(RCClassStore!C*)value._rcClassStore; 67 if (_rcClassStore) 68 _rcClassStore.refCount++; 69 return this; 70 } /// 71 72 inout(T) opCast(T)() inout 73 if (is(T == RCClass!U, U) && is(typeof({C c; U u = cast(U)c;}))) 74 { 75 static if (!is(T == RCClass!U, U)) // Destructure into U 76 assert(false); 77 inout(T) result; 78 // Check if dynamic cast is valid 79 if (!cast(U)this._rcClassGet()) 80 return result; 81 cast()result._rcClassStore = cast(typeof(result._rcClassStore))_rcClassStore; 82 if (_rcClassStore) 83 (cast()_rcClassStore.refCount)++; 84 return result; 85 } /// 86 87 bool opCast(T)() const 88 if (is(T == bool)) 89 { 90 return !!_rcClassStore; 91 } /// 92 93 auto opCall(Args...)(auto ref Args args) 94 if (is(typeof(_rcClassGet.opCall(args)))) 95 { 96 return _rcClassGet.opCall(args); 97 } /// 98 99 // lifetime 100 101 void _rcClassDestroy() 102 { 103 if (_rcClassStore && --_rcClassStore.refCount == 0) 104 { 105 static if (__traits(hasMember, C, "__xdtor")) 106 _rcClassGet.__xdtor(); 107 GC.free(_rcClassStore); 108 } 109 } 110 111 this(this) 112 { 113 if (_rcClassStore) 114 _rcClassStore.refCount++; 115 } 116 117 ~this() 118 { 119 _rcClassDestroy(); 120 } 121 } 122 123 /// Constructs a new reference-counted instance of `C`. 124 /// (An external factory function is used instead of `static opCall` 125 /// to avoid conflicting with the class's non-static `opCall`.) 126 template rcClass(C) 127 if (is(C == class)) 128 { 129 RCClass!C rcClass(Args...)(auto ref Args args) 130 if (is(C == class) && is(typeof(emplace!C(null, args)))) 131 { 132 RCClass!C c; 133 c._rcClassStore = new RCClassStore!C; 134 c._rcClassStore.refCount = 1; 135 emplace!C(c._rcClassStore.data[], args); 136 return c; 137 } 138 } 139 140 /// Constructors 141 unittest 142 { 143 void ctorTest(bool haveArglessCtor, bool haveArgCtor)() 144 { 145 static class C 146 { 147 int n = -1; 148 149 static if (haveArglessCtor) 150 this() { n = 1; } 151 152 static if (haveArgCtor) 153 this(int val) { n = val; } 154 155 ~this() { n = -2; } 156 } 157 158 RCClass!C rc; 159 assert(!rc); 160 161 static if (haveArglessCtor || !haveArgCtor) 162 { 163 rc = rcClass!C(); 164 assert(rc); 165 static if (haveArglessCtor) 166 assert(rc.n == 1); 167 else 168 assert(rc.n == -1); // default value 169 } 170 else 171 static assert(!is(typeof(rcClass!C()))); 172 173 static if (haveArgCtor) 174 { 175 rc = rcClass!C(42); 176 assert(rc); 177 assert(rc.n == 42); 178 } 179 else 180 static assert(!is(typeof(rcClass!C(1)))); 181 182 rc = null; 183 assert(!rc); 184 } 185 186 import std.meta : AliasSeq; 187 foreach (haveArglessCtor; AliasSeq!(false, true)) 188 foreach (haveArgCtor; AliasSeq!(false, true)) 189 ctorTest!(haveArglessCtor, haveArgCtor); 190 } 191 192 /// Lifetime 193 unittest 194 { 195 static class C 196 { 197 static int counter; 198 199 this() { counter++; } 200 ~this() { counter--; } 201 } 202 203 { 204 auto a = rcClass!C(); 205 assert(C.counter == 1); 206 auto b = a; 207 assert(C.counter == 1); 208 } 209 assert(C.counter == 0); 210 } 211 212 /// Inheritance 213 unittest 214 { 215 static class Base 216 { 217 int foo() { return 1; } 218 } 219 220 static class Derived : Base 221 { 222 override int foo() { return 2; } 223 } 224 225 auto derived = rcClass!Derived(); 226 RCClass!Base base = derived; // initialization 227 base = derived; // assignment 228 static assert(!is(typeof(derived = base))); 229 auto base2 = cast(RCClass!Base)derived; 230 } 231 232 /// Non-static opCall 233 unittest 234 { 235 static class C 236 { 237 int calls; 238 void opCall() { calls++; } 239 } 240 241 auto c = rcClass!C(); 242 assert(c.calls == 0); 243 c(); 244 assert(c.calls == 1); 245 } 246 247 /// Casting 248 unittest 249 { 250 static class A {} 251 static class B : A {} 252 static class C : A {} 253 RCClass!A a = rcClass!B(); 254 assert( cast(RCClass!B)a); 255 assert(!cast(RCClass!C)a); 256 }