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