1 module libd.datastructures.smartptr; 2 3 import libd.algorithm : any; 4 import libd.memory : move, emplaceInit, AllocatorWrapperOf, SystemAllocator, maybeNull; 5 import libd.meta : TypeId, TypeIdOf, isCopyable; 6 7 private mixin template accessFuncs(bool TypeHasT) 8 { 9 static if(TypeHasT) 10 { 11 void access()(scope void delegate(T) @nogc nothrow func) 12 { 13 static assert(false, "Please mark the parameter as `scope ref`"); 14 } 15 16 void access()(scope void function(T) @nogc nothrow func) 17 { 18 static assert(false, "Please mark the parameter as `scope ref`"); 19 } 20 21 void access(scope void delegate(scope ref T) @nogc nothrow func) { this.accessImpl(func); } 22 void access(scope void function(scope ref T) @nogc nothrow func) { this.accessImpl(func); } 23 @safe 24 void access(scope void delegate(scope ref T) @nogc @safe nothrow func) { this.accessImpl(func); } 25 @safe 26 void access(scope void function(scope ref T) @nogc @safe nothrow func) { this.accessImpl(func); } 27 } 28 else 29 { 30 void access(T)(scope void delegate(T) @nogc nothrow func) 31 { 32 static assert(false, "Please mark the parameter as `scope ref`"); 33 } 34 35 void access(T)(scope void function(T) @nogc nothrow func) 36 { 37 static assert(false, "Please mark the parameter as `scope ref`"); 38 } 39 40 void access(T)(scope void delegate(scope ref T) @nogc nothrow func) { this.accessImpl!T(func); } 41 void access(T)(scope void function(scope ref T) @nogc nothrow func) { this.accessImpl!T(func); } 42 @safe 43 void access(T)(scope void delegate(scope ref T) @nogc @safe nothrow func) { this.accessImpl!T(func); } 44 @safe 45 void access(T)(scope void function(scope ref T) @nogc @safe nothrow func) { this.accessImpl!T(func); } 46 } 47 } 48 49 struct Unique(alias T) 50 { 51 private T _value; 52 private bool _set; 53 54 @disable this(this){} 55 56 @nogc nothrow: 57 58 this()(auto ref T value) 59 { 60 this = value; 61 } 62 63 this(scope ref return typeof(this) rhs) 64 { 65 move(rhs._value, this._value); 66 this._set = true; 67 rhs._set = false; 68 } 69 70 private void accessImpl(T)(T func) 71 { 72 assert(!this.isNull, "This Unique is null"); 73 func(this._value); 74 } 75 76 mixin accessFuncs!true; 77 78 void opAssign()(auto ref T value) 79 { 80 move(value, this._value); 81 this._set = true; 82 } 83 84 void opAssign()(typeof(null) n) 85 { 86 emplaceInit(this._value); 87 this._set = false; 88 } 89 90 void release()(scope ref return T dest) 91 { 92 move(this._value, dest); 93 this._set = false; 94 } 95 96 void release()(scope ref return typeof(this) dest) 97 { 98 move(this._value, dest._value); 99 this._set = false; 100 dest._set = true; 101 } 102 103 typeof(this) release() 104 { 105 typeof(this) ret; 106 this.release(ret); 107 return ret; // NRVO makes this possible for non-copyable types. 108 } 109 110 T releaseRaw()() 111 { 112 T ret; 113 move(this._value, ret); 114 this._set = false; 115 return ret; 116 } 117 118 @property @safe 119 bool isNull() const 120 { 121 return !this._set; 122 } 123 124 @property 125 inout(T)* ptrUnsafe() inout 126 { 127 assert(!this.isNull, "This Unique is null."); 128 return &this._value; 129 } 130 } 131 /// 132 @("Unique") 133 unittest 134 { 135 int dtor = 0; 136 static struct S 137 { 138 int num; 139 int* dtor; 140 @disable this(this){} 141 @nogc nothrow ~this() 142 { 143 if(dtor) 144 (*dtor)++; 145 } 146 } 147 148 S s = S(200, &dtor); 149 Unique!S a = s; 150 Unique!S b; 151 152 assert(s == S.init); 153 assert(!a.isNull); 154 assert(b.isNull); 155 assert(a.ptrUnsafe.num == 200); 156 a.access((scope ref v) 157 { 158 assert(v.num == 200); 159 v.num *= 2; 160 }); 161 162 move(a, b); 163 assert(a.isNull); 164 assert(a._value == S.init); 165 assert(!b.isNull); 166 assert(b.ptrUnsafe.num == 400); 167 168 a = Unique!S(S(800, &dtor)); 169 assert(!a.isNull); 170 assert(a.ptrUnsafe.num == 800); 171 a = null; 172 assert(a.isNull); 173 assert(a._value == S.init); 174 assert(dtor == 1); 175 176 b.release(s); 177 assert(b.isNull); 178 assert(s.num == 400); 179 180 a = s; 181 a.release(b); 182 assert(a.isNull); 183 assert(!b.isNull); 184 185 void test(Unique!S s) 186 { 187 assert(!s.isNull); 188 auto ss = s.releaseRaw(); 189 assert(s.isNull); 190 assert(ss.num == 400); 191 } 192 193 test(b.release); 194 assert(dtor == 2); 195 196 Unique!S test2(Unique!S s) 197 { 198 assert(!s.isNull); 199 return s.release; 200 } 201 202 a = S(200, &dtor); 203 b = test2(a.release); 204 assert(dtor == 2); 205 assert(a.isNull); 206 assert(!b.isNull); 207 b.__xdtor(); 208 assert(dtor == 3); 209 } 210 211 Unique!T makeUnique(T)(auto ref T value) 212 { 213 return typeof(return)(value); 214 } 215 216 struct Shared(alias T, alias AllocT = SystemAllocator) 217 { 218 private static struct Store 219 { 220 T value; 221 ulong count; 222 } 223 224 private Store* _store; 225 private AllocatorWrapperOf!AllocT _alloc; 226 227 @nogc nothrow: 228 229 @safe 230 this(AllocatorWrapperOf!AllocT alloc) 231 { 232 this._alloc = alloc; 233 } 234 235 this()(auto ref T value, AllocatorWrapperOf!AllocT alloc = AllocatorWrapperOf!AllocT.init) 236 { 237 this._alloc = alloc; 238 this.createNewStore(value); 239 } 240 241 this(scope ref return typeof(this) copy) 242 { 243 auto oldStore = this._store; 244 245 this._store = copy._store; 246 this._alloc = copy._alloc; 247 if(this._store !is null) 248 this.refUp(); 249 if(oldStore !is null) 250 this.refDown(); 251 } 252 253 private void accessImpl(T)(T func) 254 { 255 assert(!this.isNull, "This Shared is null."); 256 func(this._store.value); 257 } 258 259 mixin accessFuncs!true; 260 261 ~this() 262 { 263 if(!this.isNull) 264 this.refDown(); 265 } 266 267 void createNewStore()(auto ref T value) 268 { 269 if(this._store !is null) 270 this.refDown(); 271 this._store = this._alloc.make!Store(); 272 if(this._store is null) 273 assert(false, "Memory allocation failed."); 274 move(value, this._store.value); 275 this.refUp(); 276 } 277 278 void opAssign()(typeof(null) n) 279 { 280 if(this._store !is null) 281 this.refDown(); 282 this._store = null; 283 } 284 285 @property @safe 286 bool isNull() const 287 { 288 return this._store is null; 289 } 290 291 @property 292 inout(T)* ptrUnsafe() inout 293 { 294 assert(!this.isNull, "This Shared is null."); 295 return &this._store.value; 296 } 297 298 @safe 299 private void refUp() 300 { 301 this._store.count++; 302 } 303 304 private void refDown()() 305 { 306 this._store.count--; 307 if(this._store.count == 0) 308 { 309 this._alloc.dispose(this._store.maybeNull!(AllocT.Tag)); 310 this._store = null; 311 } 312 } 313 } 314 /// 315 @("Shared") 316 unittest 317 { 318 int dtor; 319 static struct S 320 { 321 int value; 322 int* dtor; 323 @disable this(this); 324 325 ~this() @nogc nothrow 326 { 327 if(dtor) 328 (*dtor)++; 329 } 330 } 331 332 // Simple 333 { 334 auto a = S(200, &dtor).makeShared; 335 } 336 assert(dtor == 1); 337 dtor = 0; 338 339 // Copying 340 { 341 auto a = S(200, &dtor).makeShared; 342 auto b = a; 343 } 344 assert(dtor == 1); 345 dtor = 0; 346 347 // Copying over existing 348 { 349 auto a = S(200, &dtor).makeShared; 350 auto b = S(400, &dtor).makeShared; 351 b = a; 352 } 353 assert(dtor == 2); 354 dtor = 0; 355 356 // Null assign 357 { 358 auto a = S(200, &dtor).makeShared; 359 a = null; 360 assert(dtor == 1); 361 } 362 363 // Access 364 { 365 auto a = S(200, &dtor).makeShared; 366 auto b = a; 367 368 b.access((scope ref v) 369 { 370 v.value *= 2; 371 }); 372 assert(a.ptrUnsafe.value == b.ptrUnsafe.value); 373 assert(a.ptrUnsafe.value == 400); 374 } 375 } 376 377 Shared!(T, AllocT) makeShared(T, alias AllocT = SystemAllocator)(auto ref T value, AllocatorWrapperOf!AllocT alloc = AllocatorWrapperOf!AllocT.init) 378 { 379 return typeof(return)(value, alloc); 380 } 381 382 struct TypedPtrBase(alias AllocT = SystemAllocator) 383 { 384 private void* _ptr; 385 private TypeId _id; 386 private AllocatorWrapperOf!AllocT _alloc; 387 388 @disable this(this){} 389 390 @nogc nothrow: 391 392 this(AllocatorWrapperOf!AllocT alloc) 393 { 394 this._alloc = alloc; 395 } 396 397 this(T)(auto ref T value, AllocatorWrapperOf!AllocT alloc = AllocatorWrapperOf!AllocT.init) 398 { 399 this._alloc = alloc; 400 this.setByForce(value); 401 } 402 403 ~this() 404 { 405 if(this._ptr !is null) 406 { 407 this._alloc.dispose(this._ptr.maybeNull!(AllocT.Tag)); 408 this._ptr = null; 409 } 410 } 411 412 @trusted // Because ptrUnsafeAs is not @safe. It technically *is* @safe by itself due to the safety checks, but the user can use it to perform @system behaviour. 413 private void accessImpl(ValueT, FuncT)(FuncT func) 414 { 415 assert(!this.isNull, "This TypedPtr is null."); 416 func(*this.ptrUnsafeAs!ValueT); 417 } 418 419 mixin accessFuncs!false; 420 421 @safe 422 bool contains(alias T)() const 423 { 424 assert(!this.isNull, "This TypedPtr is null."); 425 return this._id == TypeIdOf!T; 426 } 427 428 void setByForce(T)(auto ref T value) 429 { 430 static if(is(T == struct)) 431 static assert(__traits(isPOD, T), "Type `"~T.stringof~"` must be a POD struct."); 432 433 if(this._ptr is null) 434 { 435 this._ptr = this._alloc.make!T(); 436 if(this._ptr is null) 437 assert(false, "Memory allocation failed."); 438 } 439 else if(this._id != TypeIdOf!value) 440 { 441 this._alloc.dispose(this._ptr.maybeNull!(AllocT.Tag)); 442 this._ptr = null; 443 this.setByForce(value); 444 return; 445 } 446 447 move(value, *(cast(T*)this._ptr)); 448 this._id = TypeIdOf!T; 449 } 450 451 void opAssign(T)(auto ref T value) 452 { 453 assert( 454 this.isNull || this._id == TypeIdOf!T, 455 "opAssign cannot store a value of a different type from the current value. Use `setByForce` for that." 456 ); 457 this.setByForce(value); 458 } 459 460 void opAssign()(typeof(null) n) 461 { 462 if(!this.isNull) 463 { 464 this._id = TypeId.init; 465 this._alloc.dispose(this._ptr); 466 } 467 } 468 469 @property @safe 470 bool isNull() const 471 { 472 return this._ptr is null; 473 } 474 475 @property 476 inout(void*) ptrUnsafe() inout 477 { 478 assert(!this.isNull, "This TypedPtr is null."); 479 return this._ptr; 480 } 481 482 @property 483 inout(T)* ptrUnsafeAs(T)() inout 484 { 485 assert(!this.isNull, "This TypePtr is null."); 486 assert(this._id == TypeIdOf!T, "Type mismatch. This TypedPtr does not store `"~T.stringof~"`"); 487 return cast(inout(T)*)this._ptr; 488 } 489 } 490 alias TypedPtr = TypedPtrBase!(); 491 /// 492 @("TypedPtr") 493 unittest 494 { 495 static struct S 496 { 497 int value; 498 } 499 500 auto ptr = S(200).makeTyped; 501 assert(!ptr.isNull); 502 assert(ptr.contains!S); 503 assert(ptr.ptrUnsafeAs!S.value == 200); 504 ptr.access!S((scope ref s) 505 { 506 s.value *= 2; 507 }); 508 assert(ptr.ptrUnsafeAs!S.value == 400); 509 ptr = S(100); 510 assert(ptr.ptrUnsafeAs!S.value == 100); 511 512 // opAssign cannot change types. 513 // bool threw = false; 514 // try ptr = 200; 515 // catch(Error error) 516 // threw = true; 517 // assert(threw); 518 519 // But setForce can 520 ptr.setByForce(200); 521 assert(ptr.contains!int); 522 assert(*ptr.ptrUnsafeAs!int == 200); 523 524 ptr = null; 525 assert(ptr.isNull); 526 } 527 528 TypedPtrBase!AllocT makeTyped(T, alias AllocT = SystemAllocator)(auto ref T value, AllocatorWrapperOf!AllocT alloc = AllocatorWrapperOf!AllocT.init) 529 { 530 return typeof(return)(value, alloc); 531 }