1 /* 2 MIT License 3 Copyright (c) 2017 Boris Barboris 4 */ 5 6 module daii.refcounted; 7 8 import core.atomic: atomicOp; 9 import std.experimental.allocator: make, dispose; 10 import std.experimental.allocator.mallocator: Mallocator; 11 import std.functional: forward; 12 import std.traits: isArray, isAbstractClass, isAssignable; 13 14 import daii.utils; 15 16 /// Reference-counting memory owner, holds one shared instance of type T. 17 /// Don't use it to hold built-in arrays, use custom array type instead. 18 /// Deallocates in destructor, when reference count is zero. 19 struct RefCounted(T, bool Atomic = true, Allocator = Mallocator) 20 if (isAllocator!Allocator && !isArray!T) 21 { 22 enum HoldsAllocator = !isStaticAllocator!Allocator; 23 24 static if (isClassOrIface!T) 25 { 26 alias PtrT = T; 27 @property inout(T) v() inout @nogc @safe 28 { 29 assert(valid); 30 return ptr; 31 } 32 } 33 else 34 { 35 alias PtrT = T*; 36 @property ref inout(T) v() inout @nogc @safe 37 { 38 assert(valid); 39 return *ptr; 40 } 41 } 42 43 static if (Atomic) 44 alias RefCounterT = shared size_t; 45 else 46 alias RefCounterT = size_t; 47 48 private RefCounterT* refcount; 49 private PtrT ptr; 50 51 @property bool valid() const @nogc @safe { return *refcount > 0; } 52 53 @disable this(); 54 55 static if (HoldsAllocator) 56 private Allocator allocator; 57 58 // don't generate constructors for abstract types 59 static if (!(is(T == interface) || isAbstractClass!(T))) 60 { 61 static if (!HoldsAllocator) 62 { 63 private this(PtrT ptr) @trusted 64 { 65 this.refcount = cast(RefCounterT*) Allocator.instance.make!size_t(1); 66 this.ptr = ptr; 67 } 68 69 static RefCounted!(T, Atomic, Allocator) 70 make(Args...)(auto ref Args args) @trusted 71 { 72 auto ptr = Allocator.instance.make!(T)(forward!args); 73 auto rq = RefCounted!(T, Atomic, Allocator)(ptr); 74 assert(rq.valid); 75 return rq; 76 } 77 } 78 else 79 { 80 private this(PtrT ptr, Allocator alloc) @trusted 81 { 82 this.refcount = cast(RefCounterT*) alloc.make!size_t(1); 83 this.ptr = ptr; 84 this.allocator = alloc; 85 } 86 87 static RefCounted!(T, Atomic, Allocator) 88 make(Args...)(auto ref Allocator alloc, auto ref Args args) @trusted 89 { 90 auto ptr = alloc.make!(T)(forward!args); 91 auto rq = RefCounted!(T, Atomic, Allocator)(ptr, alloc); 92 assert(rq.valid); 93 return rq; 94 } 95 } 96 } 97 98 // bread and butter 99 ~this() @safe 100 { 101 decrement(); 102 } 103 104 // destroy the resource (destructor + free memory) 105 private void destroy() @trusted 106 { 107 static if (HoldsAllocator) 108 allocator.dispose(ptr); 109 else 110 Allocator.instance.dispose(ptr); 111 } 112 113 private void decrement() @safe 114 { 115 assert(valid); 116 static if (Atomic) 117 { 118 if (atomicOp!"-="(*refcount, 1) == 0) 119 destroy(); 120 } 121 else 122 { 123 if ((*refcount -= 1) == 0) 124 destroy(); 125 } 126 } 127 128 private void increment() @safe @nogc 129 { 130 assert(valid); 131 static if (Atomic) 132 atomicOp!"+="(*refcount, 1); 133 else 134 *refcount += 1; 135 } 136 137 this(this) @safe @nogc 138 { 139 increment(); 140 } 141 142 // handle polymorphism 143 static if (isClassOrIface!T) 144 { 145 // Polymorphism-aware assign operator 146 ref RefCounted!(T, Atomic, Allocator) 147 opAssign(DT)(const RefCounted!(DT, Atomic, Allocator) rhs) @trusted 148 if (isClassOrIface!DT && isAssignable!(T, DT)) 149 { 150 decrement(); 151 this.ptr = cast(PtrT) rhs.ptr; 152 this.refcount = cast(RefCounterT*) rhs.refcount; 153 static if (HoldsAllocator) 154 this.allocator = cast(Allocator) rhs.allocator; 155 increment(); 156 return this; 157 } 158 159 // Polymorphism-aware constructor 160 this(DT)(const RefCounted!(DT, Atomic, Allocator) rhs) @trusted 161 if (isClassOrIface!DT && isAssignable!(T, DT)) 162 { 163 this.ptr = cast(PtrT) rhs.ptr; 164 this.refcount = cast(RefCounterT*) rhs.refcount; 165 static if (HoldsAllocator) 166 this.allocator = cast(Allocator) rhs.allocator; 167 increment(); 168 } 169 170 // Polymorphic upcast 171 RefCounted!(BT, Atomic, Allocator) to(BT)() const @safe 172 if (isClassOrIface!BT && isAssignable!(BT, T)) 173 { 174 assert(valid); 175 RefCounted!(BT, Atomic, Allocator) rv = 176 RefCounted!(BT, Atomic, Allocator)(this); 177 assert(rv.valid); 178 return rv; 179 } 180 } 181 else 182 { 183 ref opAssign(const RefCounted!(T, Atomic, Allocator) rhs) @trusted 184 { 185 decrement(); 186 this.ptr = cast(PtrT) rhs.ptr; 187 this.refcount = cast(RefCounterT*) rhs.refcount; 188 static if (HoldsAllocator) 189 this.allocator = cast(Allocator) rhs.allocator; 190 increment(); 191 return this; 192 } 193 } 194 } 195 196 unittest 197 { 198 auto uq = RefCounted!int.make(5); 199 assert(uq.valid); 200 assert(uq.v == 5); 201 uq.v = 7; 202 assert(uq.v == 7); 203 } 204 205 unittest 206 { 207 auto create() 208 { 209 return RefCounted!int.make(0); 210 } 211 void consume(RefCounted!int u) 212 { 213 assert(u.valid); 214 assert(*u.refcount == 2); 215 assert(u.v == 5); 216 } 217 auto u = create(); 218 assert(u.valid); 219 assert(*u.refcount == 1); 220 assert(u.v == 0); 221 u.v = 5; 222 consume(u); 223 assert(*u.refcount == 1); 224 } 225 226 unittest 227 { 228 struct TS 229 { 230 int x = -3; 231 } 232 auto u = RefCounted!TS.make; 233 assert(u.v.x == -3); 234 } 235 236 unittest 237 { 238 static int count = 0; 239 static int total = 0; 240 class TC 241 { 242 this() { count++; total++; } 243 ~this() { count--; } 244 int j = 3; 245 } 246 auto u1 = RefCounted!TC.make; 247 assert(count == 1); 248 { 249 auto u2 = RefCounted!TC.make; 250 assert(count == 2); 251 assert(u2.v !is null); 252 assert(u2.v.j == 3); 253 u2 = u1; 254 assert(count == 1); 255 assert(u1.valid); 256 assert(u2.valid); 257 assert(*(u1.refcount) == 2); 258 } 259 assert(count == 1); 260 assert(*(u1.refcount) == 1); 261 assert(total == 2); 262 } 263 264 import std.experimental.allocator.showcase; 265 266 unittest 267 { 268 static int count = 0; 269 static int total = 0; 270 class TC 271 { 272 this() { count++; total++; } 273 ~this() { count--; } 274 int j = 3; 275 } 276 alias Alloc = StackFront!(4096, Mallocator); 277 Alloc al; 278 alias Uq = RefCounted!(TC, true, Alloc*); 279 auto u1 = Uq.make(&al); 280 assert(count == 1); 281 { 282 auto u2 = Uq.make(&al); 283 assert(count == 2); 284 assert(u2.v !is null); 285 assert(u2.v.j == 3); 286 u2 = u1; 287 assert(count == 1); 288 assert(u1.valid); 289 assert(u2.valid); 290 assert(*(u1.refcount) == 2); 291 } 292 assert(count == 1); 293 assert(*(u1.refcount) == 1); 294 assert(total == 2); 295 } 296 297 unittest 298 { 299 static int ades = 0; 300 static int bdes = 0; 301 class A { ~this() {ades++;}} 302 class B: A { ~this(){bdes++;}} 303 { 304 RefCounted!A ag = RefCounted!A.make(); 305 { 306 RefCounted!A aq = ag; 307 assert(aq.valid && ag.valid); 308 { 309 RefCounted!A bq = RefCounted!B.make(); 310 assert(ades == 0); 311 assert(bdes == 0); 312 } 313 assert(ades == 1); 314 assert(bdes == 1); 315 } 316 assert(ades == 1); 317 assert(bdes == 1); 318 } 319 assert(ades == 2); 320 assert(bdes == 1); 321 } 322 323 unittest 324 { 325 static int ades = 0; 326 static int bdes = 0; 327 class A { ~this() {ades++;}} 328 class B: A { ~this(){bdes++;}} 329 void consume(RefCounted!A uq) 330 { 331 assert(uq.valid); 332 } 333 { 334 RefCounted!B aq = RefCounted!B.make(); 335 consume(aq.to!A); 336 assert(ades == 0); 337 assert(bdes == 0); 338 } 339 assert(ades == 1); 340 assert(bdes == 1); 341 }