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