1 module automem.unique; 2 3 import automem.test_utils: TestUtils; 4 import automem.traits: isAllocator; 5 import std.experimental.allocator: theAllocator; 6 import std.typecons: Flag; 7 8 version(unittest) { 9 import unit_threaded; 10 import test_allocator: TestAllocator; 11 } 12 13 mixin TestUtils; 14 15 struct Unique(Type, Allocator = typeof(theAllocator()), 16 Flag!"supportGC" supportGC = Flag!"supportGC".yes) 17 if(isAllocator!Allocator) { 18 19 import std.traits: hasMember; 20 import std.typecons: Proxy; 21 22 enum isSingleton = hasMember!(Allocator, "instance"); 23 enum isTheAllocator = is(Allocator == typeof(theAllocator)); 24 enum isGlobal = isSingleton || isTheAllocator; 25 26 static if(is(Type == class)) 27 alias Pointer = Type; 28 else 29 alias Pointer = Type*; 30 31 static if(isGlobal) { 32 33 /** 34 The allocator is global, so no need to pass it in to the constructor 35 */ 36 this(Args...)(auto ref Args args) { 37 this.makeObject!(supportGC, args)(); 38 } 39 40 } else { 41 42 /** 43 Non-singleton allocator, must be passed in 44 */ 45 46 this(Args...)(Allocator allocator, auto ref Args args) { 47 _allocator = allocator; 48 this.makeObject!(supportGC, args)(); 49 } 50 } 51 52 53 static if(isGlobal) 54 /** 55 Factory method so can construct with zero args. 56 */ 57 static typeof(this) construct(Args...)(auto ref Args args) { 58 static if (Args.length != 0) 59 return typeof(return)(args); 60 else { 61 typeof(return) ret; 62 ret.makeObject!(supportGC)(); 63 return ret; 64 } 65 } 66 else 67 /** 68 Factory method. Not necessary with non-global allocator 69 but included for symmetry. 70 */ 71 static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) { 72 return typeof(return)(allocator, args); 73 } 74 75 this(T)(Unique!(T, Allocator) other) if(is(T: Type)) { 76 moveFrom(other); 77 } 78 79 @disable this(this); 80 81 ~this() { 82 deleteObject; 83 } 84 85 /** 86 Gets the owned pointer. Use with caution. 87 */ 88 inout(Pointer) get() inout @system { 89 return _object; 90 } 91 92 /** 93 Releases ownership and transfers it to the returned 94 Unique object. 95 */ 96 Unique unique() { 97 import std.algorithm: move; 98 Unique u; 99 move(this, u); 100 assert(_object is null); 101 return u; 102 } 103 104 package Pointer release() { 105 auto ret = _object; 106 _object = null; 107 return ret; 108 } 109 110 package Allocator allocator() { 111 return _allocator; 112 } 113 114 /** 115 "Truthiness" cast 116 */ 117 bool opCast(T)() const if(is(T == bool)) { 118 return _object !is null; 119 } 120 121 void opAssign(T)(Unique!(T, Allocator) other) if(is(T: Type)) { 122 deleteObject; 123 moveFrom(other); 124 } 125 126 mixin Proxy!_object; 127 128 private: 129 130 Pointer _object; 131 132 static if(isSingleton) 133 alias _allocator = Allocator.instance; 134 else static if(isTheAllocator) 135 alias _allocator = theAllocator; 136 else 137 Allocator _allocator; 138 139 void deleteObject() @safe { 140 import automem.allocator: dispose; 141 import std.traits: isPointer; 142 import std.traits : hasIndirections; 143 import core.memory : GC; 144 145 static if(isPointer!Allocator) 146 assert(_object is null || _allocator !is null); 147 148 if(_object !is null) () @trusted { _allocator.dispose(_object); }(); 149 static if (is(Type == class)) { 150 // need to watch the monitor pointer even if supportGC is false. 151 () @trusted { 152 auto repr = (cast(void*)_object)[0..__traits(classInstanceSize, Type)]; 153 GC.removeRange(&repr[(void*).sizeof]); 154 }(); 155 } else static if (supportGC && hasIndirections!Type) { 156 () @trusted { 157 GC.removeRange(_object); 158 }(); 159 } 160 } 161 162 void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) { 163 _object = other._object; 164 other._object = null; 165 166 static if(!isGlobal) { 167 import std.algorithm: move; 168 _allocator = other._allocator.move; 169 } 170 } 171 } 172 173 private template makeObject(Flag!"supportGC" supportGC, args...) 174 { 175 void makeObject(Type,A)(ref Unique!(Type, A) u) { 176 import std.experimental.allocator: make; 177 import std.functional : forward; 178 import std.traits : hasIndirections; 179 import core.memory : GC; 180 181 u._object = u._allocator.make!Type(forward!args); 182 183 static if (is(Type == class)) { 184 () @trusted { 185 auto repr = (cast(void*)u._object)[0..__traits(classInstanceSize, Type)]; 186 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) { 187 GC.addRange(&repr[(void*).sizeof], 188 __traits(classInstanceSize, Type) - (void*).sizeof); 189 } else { 190 // need to watch the monitor pointer even if supportGC is false. 191 GC.addRange(&repr[(void*).sizeof], (void*).sizeof); 192 } 193 }(); 194 } else static if (supportGC && hasIndirections!Type) { 195 () @trusted { 196 GC.addRange(u._object, Type.sizeof); 197 }(); 198 } 199 } 200 } 201 202 @("with struct and test allocator") 203 @system unittest { 204 205 auto allocator = TestAllocator(); 206 { 207 const foo = Unique!(Struct, TestAllocator*)(&allocator, 5); 208 foo.twice.shouldEqual(10); 209 allocator.numAllocations.shouldEqual(1); 210 Struct.numStructs.shouldEqual(1); 211 } 212 213 Struct.numStructs.shouldEqual(0); 214 } 215 216 @("with class and test allocator") 217 @system unittest { 218 219 auto allocator = TestAllocator(); 220 { 221 const foo = Unique!(Class, TestAllocator*)(&allocator, 5); 222 foo.twice.shouldEqual(10); 223 allocator.numAllocations.shouldEqual(1); 224 Class.numClasses.shouldEqual(1); 225 } 226 227 Class.numClasses.shouldEqual(0); 228 } 229 230 231 @("with struct and mallocator") 232 @system unittest { 233 234 import std.experimental.allocator.mallocator: Mallocator; 235 { 236 const foo = Unique!(Struct, Mallocator)(5); 237 foo.twice.shouldEqual(10); 238 Struct.numStructs.shouldEqual(1); 239 } 240 241 Struct.numStructs.shouldEqual(0); 242 } 243 244 245 @("default constructor") 246 @system unittest { 247 auto allocator = TestAllocator(); 248 249 auto ptr = Unique!(Struct, TestAllocator*)(); 250 (cast(bool)ptr).shouldBeFalse; 251 ptr.get.shouldBeNull; 252 253 ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 254 ptr.get.shouldNotBeNull; 255 ptr.get.twice.shouldEqual(10); 256 (cast(bool)ptr).shouldBeTrue; 257 } 258 259 @(".init") 260 @system unittest { 261 auto allocator = TestAllocator(); 262 263 Unique!(Struct, TestAllocator*) ptr; 264 (cast(bool)ptr).shouldBeFalse; 265 ptr.get.shouldBeNull; 266 267 ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 268 ptr.get.shouldNotBeNull; 269 ptr.get.twice.shouldEqual(10); 270 (cast(bool)ptr).shouldBeTrue; 271 } 272 273 @("move") 274 @system unittest { 275 import std.algorithm: move; 276 277 auto allocator = TestAllocator(); 278 auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5); 279 Unique!(Struct, TestAllocator*) newPtr = oldPtr.move; 280 oldPtr.shouldBeNull; 281 newPtr.twice.shouldEqual(10); 282 Struct.numStructs.shouldEqual(1); 283 } 284 285 @("copy") 286 @system unittest { 287 auto allocator = TestAllocator(); 288 auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5); 289 Unique!(Struct, TestAllocator*) newPtr; 290 // non-copyable 291 static assert(!__traits(compiles, newPtr = oldPtr)); 292 } 293 294 @("construct base class") 295 @system unittest { 296 auto allocator = TestAllocator(); 297 { 298 Unique!(Object, TestAllocator*) bar = Unique!(Class, TestAllocator*)(&allocator, 5); 299 Class.numClasses.shouldEqual(1); 300 } 301 302 Class.numClasses.shouldEqual(0); 303 } 304 305 @("assign base class") 306 @system unittest { 307 auto allocator = TestAllocator(); 308 { 309 Unique!(Object, TestAllocator*) bar; 310 bar = Unique!(Class, TestAllocator*)(&allocator, 5); 311 Class.numClasses.shouldEqual(1); 312 } 313 314 Class.numClasses.shouldEqual(0); 315 } 316 317 @("Return Unique from function") 318 @system unittest { 319 auto allocator = TestAllocator(); 320 321 auto produce(int i) { 322 return Unique!(Struct, TestAllocator*)(&allocator, i); 323 } 324 325 auto ptr = produce(4); 326 ptr.twice.shouldEqual(8); 327 } 328 329 @("unique") 330 @system unittest { 331 auto allocator = TestAllocator(); 332 auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5); 333 auto newPtr = oldPtr.unique; 334 newPtr.twice.shouldEqual(10); 335 oldPtr.shouldBeNull; 336 } 337 338 @("@nogc") 339 @system @nogc unittest { 340 341 import std.experimental.allocator.mallocator: Mallocator; 342 343 { 344 const ptr = Unique!(NoGcStruct, Mallocator)(5); 345 // shouldEqual isn't @nogc 346 assert(ptr.i == 5); 347 assert(NoGcStruct.numStructs == 1); 348 } 349 350 assert(NoGcStruct.numStructs == 0); 351 } 352 353 @("@nogc @safe") 354 @safe @nogc unittest { 355 356 auto allocator = SafeAllocator(); 357 358 { 359 const ptr = Unique!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6); 360 // shouldEqual isn't @nogc 361 assert(ptr.i == 6); 362 assert(NoGcStruct.numStructs == 1); 363 } 364 365 assert(NoGcStruct.numStructs == 0); 366 } 367 368 @("deref") 369 @system unittest { 370 { 371 auto allocator = TestAllocator(); 372 auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 373 *ptr = Struct(13); 374 ptr.twice.shouldEqual(26); 375 Struct.numStructs.shouldEqual(1); 376 } 377 Struct.numStructs.shouldEqual(0); 378 } 379 380 @("move from populated other unique") 381 @system unittest { 382 383 import std.algorithm: move; 384 385 { 386 auto allocator = TestAllocator(); 387 388 auto ptr1 = Unique!(Struct, TestAllocator*)(&allocator, 5); 389 Struct.numStructs.shouldEqual(1); 390 391 { 392 auto ptr2 = Unique!(Struct, TestAllocator*)(&allocator, 10); 393 Struct.numStructs.shouldEqual(2); 394 ptr1 = ptr2.move; 395 Struct.numStructs.shouldEqual(1); 396 ptr2.shouldBeNull; 397 ptr1.twice.shouldEqual(20); 398 } 399 400 } 401 402 Struct.numStructs.shouldEqual(0); 403 } 404 405 @("assign to rvalue") 406 @system unittest { 407 408 import std.algorithm: move; 409 410 { 411 auto allocator = TestAllocator(); 412 413 auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 414 ptr = Unique!(Struct, TestAllocator*)(&allocator, 7); 415 416 Struct.numStructs.shouldEqual(1); 417 ptr.twice.shouldEqual(14); 418 } 419 420 Struct.numStructs.shouldEqual(0); 421 } 422 423 424 @("theAllocator") 425 @system unittest { 426 with(theTestAllocator){ 427 auto ptr = Unique!Struct(42); 428 (*ptr).shouldEqual(Struct(42)); 429 Struct.numStructs.shouldEqual(1); 430 } 431 432 Struct.numStructs.shouldEqual(0); 433 } 434 435 436 @("@nogc class destructor") 437 @nogc unittest { 438 439 auto allocator = SafeAllocator(); 440 441 { 442 const ptr = Unique!(NoGcClass, SafeAllocator)(SafeAllocator(), 6); 443 // shouldEqual isn't @nogc 444 assert(ptr.i == 6); 445 assert(NoGcClass.numClasses == 1); 446 } 447 448 assert(NoGcClass.numClasses == 0); 449 } 450 451 @("Construct Unique using global allocator for struct with zero-args ctor") 452 @system unittest { 453 struct S { 454 private ulong zeroArgsCtorTest = 3; 455 } 456 auto s = Unique!S.construct(); 457 static assert(is(typeof(s) == Unique!S)); 458 assert(s._object !is null); 459 assert(s.zeroArgsCtorTest == 3); 460 }