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