1 module automem.unique; 2 3 import automem.test_utils: TestUtils; 4 import automem.traits: isAllocator; 5 import std.experimental.allocator: theAllocator; 6 7 version(unittest) { 8 import unit_threaded; 9 import test_allocator: TestAllocator; 10 } 11 12 mixin TestUtils; 13 14 struct Unique(Type, Allocator = typeof(theAllocator())) if(isAllocator!Allocator) { 15 16 import std.traits: hasMember; 17 import std.typecons: Proxy; 18 19 enum isSingleton = hasMember!(Allocator, "instance"); 20 enum isTheAllocator = is(Allocator == typeof(theAllocator)); 21 enum isGlobal = isSingleton || isTheAllocator; 22 23 static if(is(Type == class)) 24 alias Pointer = Type; 25 else 26 alias Pointer = Type*; 27 28 static if(isGlobal) { 29 30 /** 31 The allocator is global, so no need to pass it in to the constructor 32 */ 33 this(Args...)(auto ref Args args) { 34 makeObject(args); 35 } 36 37 } else { 38 39 /** 40 Non-singleton allocator, must be passed in 41 */ 42 43 this(Args...)(Allocator allocator, auto ref Args args) { 44 _allocator = allocator; 45 makeObject(args); 46 } 47 } 48 49 50 this(T)(Unique!(T, Allocator) other) if(is(T: Type)) { 51 moveFrom(other); 52 } 53 54 @disable this(this); 55 56 ~this() { 57 deleteObject; 58 } 59 60 /** 61 Gets the owned pointer. Use with caution. 62 */ 63 inout(Pointer) get() inout @system { 64 return _object; 65 } 66 67 /** 68 Releases ownership and transfers it to the returned 69 Unique object. 70 */ 71 Unique unique() { 72 import std.algorithm: move; 73 Unique u; 74 move(this, u); 75 assert(_object is null); 76 return u; 77 } 78 79 /** 80 "Truthiness" cast 81 */ 82 bool opCast(T)() const if(is(T == bool)) { 83 return _object !is null; 84 } 85 86 void opAssign(T)(Unique!(T, Allocator) other) if(is(T: Type)) { 87 deleteObject; 88 moveFrom(other); 89 } 90 91 mixin Proxy!_object; 92 93 private: 94 95 Pointer _object; 96 97 static if(isSingleton) 98 alias _allocator = Allocator.instance; 99 else static if(isTheAllocator) 100 alias _allocator = theAllocator; 101 else 102 Allocator _allocator; 103 104 void makeObject(Args...)(auto ref Args args) { 105 import std.experimental.allocator: make; 106 version(LDC) 107 _object = () @trusted { return _allocator.make!Type(args); }(); 108 else 109 _object = _allocator.make!Type(args); 110 } 111 112 void deleteObject() @safe { 113 import std.experimental.allocator: dispose; 114 import std.traits: isPointer; 115 116 static if(isPointer!Allocator) 117 assert(_object is null || _allocator !is null); 118 119 if(_object !is null) () @trusted { _allocator.dispose(_object); }(); 120 } 121 122 void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) { 123 _object = other._object; 124 other._object = null; 125 126 static if(!isGlobal) { 127 import std.algorithm: move; 128 move(other._allocator, _allocator); 129 } 130 } 131 } 132 133 134 @("with struct and test allocator") 135 @system unittest { 136 137 auto allocator = TestAllocator(); 138 { 139 const foo = Unique!(Struct, TestAllocator*)(&allocator, 5); 140 foo.twice.shouldEqual(10); 141 allocator.numAllocations.shouldEqual(1); 142 Struct.numStructs.shouldEqual(1); 143 } 144 145 Struct.numStructs.shouldEqual(0); 146 } 147 148 @("with class and test allocator") 149 @system unittest { 150 151 auto allocator = TestAllocator(); 152 { 153 const foo = Unique!(Class, TestAllocator*)(&allocator, 5); 154 foo.twice.shouldEqual(10); 155 allocator.numAllocations.shouldEqual(1); 156 Class.numClasses.shouldEqual(1); 157 } 158 159 Class.numClasses.shouldEqual(0); 160 } 161 162 163 @("with struct and mallocator") 164 @system unittest { 165 166 import std.experimental.allocator.mallocator: Mallocator; 167 { 168 const foo = Unique!(Struct, Mallocator)(5); 169 foo.twice.shouldEqual(10); 170 Struct.numStructs.shouldEqual(1); 171 } 172 173 Struct.numStructs.shouldEqual(0); 174 } 175 176 177 @("default constructor") 178 @system unittest { 179 auto allocator = TestAllocator(); 180 181 auto ptr = Unique!(Struct, TestAllocator*)(); 182 (cast(bool)ptr).shouldBeFalse; 183 ptr.get.shouldBeNull; 184 185 ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 186 ptr.get.shouldNotBeNull; 187 ptr.get.twice.shouldEqual(10); 188 (cast(bool)ptr).shouldBeTrue; 189 } 190 191 @(".init") 192 @system unittest { 193 auto allocator = TestAllocator(); 194 195 Unique!(Struct, TestAllocator*) ptr; 196 (cast(bool)ptr).shouldBeFalse; 197 ptr.get.shouldBeNull; 198 199 ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 200 ptr.get.shouldNotBeNull; 201 ptr.get.twice.shouldEqual(10); 202 (cast(bool)ptr).shouldBeTrue; 203 } 204 205 @("move") 206 @system unittest { 207 import std.algorithm: move; 208 209 auto allocator = TestAllocator(); 210 auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5); 211 Unique!(Struct, TestAllocator*) newPtr; 212 move(oldPtr, newPtr); 213 oldPtr.shouldBeNull; 214 newPtr.twice.shouldEqual(10); 215 Struct.numStructs.shouldEqual(1); 216 } 217 218 @("copy") 219 @system unittest { 220 auto allocator = TestAllocator(); 221 auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5); 222 Unique!(Struct, TestAllocator*) newPtr; 223 // non-copyable 224 static assert(!__traits(compiles, newPtr = oldPtr)); 225 } 226 227 @("construct base class") 228 @system unittest { 229 auto allocator = TestAllocator(); 230 { 231 Unique!(Object, TestAllocator*) bar = Unique!(Class, TestAllocator*)(&allocator, 5); 232 Class.numClasses.shouldEqual(1); 233 } 234 235 Class.numClasses.shouldEqual(0); 236 } 237 238 @("assign base class") 239 @system unittest { 240 auto allocator = TestAllocator(); 241 { 242 Unique!(Object, TestAllocator*) bar; 243 bar = Unique!(Class, TestAllocator*)(&allocator, 5); 244 Class.numClasses.shouldEqual(1); 245 } 246 247 Class.numClasses.shouldEqual(0); 248 } 249 250 @("Return Unique from function") 251 @system unittest { 252 auto allocator = TestAllocator(); 253 254 auto produce(int i) { 255 return Unique!(Struct, TestAllocator*)(&allocator, i); 256 } 257 258 auto ptr = produce(4); 259 ptr.twice.shouldEqual(8); 260 } 261 262 @("unique") 263 @system unittest { 264 auto allocator = TestAllocator(); 265 auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5); 266 auto newPtr = oldPtr.unique; 267 newPtr.twice.shouldEqual(10); 268 oldPtr.shouldBeNull; 269 } 270 271 @("@nogc") 272 @system @nogc unittest { 273 274 import std.experimental.allocator.mallocator: Mallocator; 275 276 { 277 const ptr = Unique!(NoGcStruct, Mallocator)(5); 278 // shouldEqual isn't @nogc 279 assert(ptr.i == 5); 280 assert(NoGcStruct.numStructs == 1); 281 } 282 283 assert(NoGcStruct.numStructs == 0); 284 } 285 286 @("@nogc @safe") 287 @safe @nogc unittest { 288 289 auto allocator = SafeAllocator(); 290 291 { 292 const ptr = Unique!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6); 293 // shouldEqual isn't @nogc 294 assert(ptr.i == 6); 295 assert(NoGcStruct.numStructs == 1); 296 } 297 298 assert(NoGcStruct.numStructs == 0); 299 } 300 301 @("deref") 302 @system unittest { 303 { 304 auto allocator = TestAllocator(); 305 auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 306 *ptr = Struct(13); 307 ptr.twice.shouldEqual(26); 308 Struct.numStructs.shouldEqual(1); 309 } 310 Struct.numStructs.shouldEqual(0); 311 } 312 313 @("move from populated other unique") 314 @system unittest { 315 316 import std.algorithm: move; 317 318 { 319 auto allocator = TestAllocator(); 320 321 auto ptr1 = Unique!(Struct, TestAllocator*)(&allocator, 5); 322 Struct.numStructs.shouldEqual(1); 323 324 { 325 auto ptr2 = Unique!(Struct, TestAllocator*)(&allocator, 10); 326 Struct.numStructs.shouldEqual(2); 327 move(ptr2, ptr1); 328 Struct.numStructs.shouldEqual(1); 329 ptr2.shouldBeNull; 330 ptr1.twice.shouldEqual(20); 331 } 332 333 } 334 335 Struct.numStructs.shouldEqual(0); 336 } 337 338 @("assign to rvalue") 339 @system unittest { 340 341 import std.algorithm: move; 342 343 { 344 auto allocator = TestAllocator(); 345 346 auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 347 ptr = Unique!(Struct, TestAllocator*)(&allocator, 7); 348 349 Struct.numStructs.shouldEqual(1); 350 ptr.twice.shouldEqual(14); 351 } 352 353 Struct.numStructs.shouldEqual(0); 354 } 355 356 357 @("theAllocator") 358 @system unittest { 359 import std.experimental.allocator: allocatorObject, dispose; 360 361 auto allocator = TestAllocator(); 362 auto oldAllocator = theAllocator; 363 scope(exit) { 364 allocator.dispose(theAllocator); 365 theAllocator = oldAllocator; 366 } 367 theAllocator = allocatorObject(allocator); 368 369 { 370 auto ptr = Unique!Struct(42); 371 (*ptr).shouldEqual(Struct(42)); 372 Struct.numStructs.shouldEqual(1); 373 } 374 375 Struct.numStructs.shouldEqual(0); 376 }