1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * Smart pointers. 7 * 8 * A smart pointer is an object that wraps a raw pointer or a reference 9 * (class, dynamic array) to manage its lifetime. 10 * 11 * This module provides two kinds of lifetime management strategies: 12 * $(UL 13 * $(LI Reference counting) 14 * $(LI Unique ownership) 15 * ) 16 * 17 * Copyright: Eugene Wissner 2016-2020. 18 * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, 19 * Mozilla Public License, v. 2.0). 20 * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) 21 * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/smartref.d, 22 * tanya/memory/smartref.d) 23 */ 24 module tanya.memory.smartref; 25 26 import tanya.memory.allocator; 27 import tanya.memory.lifetime; 28 import tanya.meta.trait; 29 30 private template Payload(T) 31 { 32 static if (isPolymorphicType!T || isDynamicArray!T) 33 { 34 alias Payload = T; 35 } 36 else 37 { 38 alias Payload = T*; 39 } 40 } 41 42 private final class RefCountedStore(T) 43 { 44 T payload; 45 size_t counter = 1; 46 47 size_t opUnary(string op)() 48 if (op == "--" || op == "++") 49 in 50 { 51 assert(this.counter > 0); 52 } 53 do 54 { 55 mixin("return " ~ op ~ "counter;"); 56 } 57 58 int opCmp(const size_t counter) 59 { 60 if (this.counter > counter) 61 { 62 return 1; 63 } 64 else if (this.counter < counter) 65 { 66 return -1; 67 } 68 else 69 { 70 return 0; 71 } 72 } 73 } 74 75 private void separateDeleter(T)(RefCountedStore!T storage, 76 shared Allocator allocator) 77 { 78 allocator.dispose(storage.payload); 79 allocator.dispose(storage); 80 } 81 82 private void unifiedDeleter(T)(RefCountedStore!T storage, 83 shared Allocator allocator) 84 { 85 auto ptr1 = finalize(storage); 86 auto ptr2 = finalize(storage.payload); 87 allocator.deallocate(ptr1.ptr[0 .. ptr1.length + ptr2.length]); 88 } 89 90 /** 91 * Reference-counted object containing a $(D_PARAM T) value as payload. 92 * $(D_PSYMBOL RefCounted) keeps track of all references of an object, and 93 * when the reference count goes down to zero, frees the underlying store. 94 * 95 * Params: 96 * T = Type of the reference-counted value. 97 */ 98 struct RefCounted(T) 99 { 100 private alias Storage = RefCountedStore!(Payload!T); 101 102 private Storage storage; 103 private void function(Storage storage, 104 shared Allocator allocator) @nogc deleter; 105 106 invariant 107 { 108 assert(this.storage is null || this.allocator_ !is null); 109 assert(this.storage is null || this.deleter !is null); 110 } 111 112 /** 113 * Takes ownership over $(D_PARAM value), setting the counter to 1. 114 * $(D_PARAM value) may be a pointer, an object or a dynamic array. 115 * 116 * Params: 117 * value = Value whose ownership is taken over. 118 * allocator = Allocator used to destroy the $(D_PARAM value) and to 119 * allocate/deallocate internal storage. 120 * 121 * Precondition: $(D_INLINECODE allocator !is null) 122 */ 123 this(Payload!T value, shared Allocator allocator = defaultAllocator) 124 { 125 this(allocator); 126 this.storage = allocator.make!Storage(); 127 this.deleter = &separateDeleter!(Payload!T); 128 129 this.storage.payload = value; 130 } 131 132 /// ditto 133 this(shared Allocator allocator) 134 in 135 { 136 assert(allocator !is null); 137 } 138 do 139 { 140 this.allocator_ = allocator; 141 } 142 143 /** 144 * Increases the reference counter by one. 145 */ 146 this(this) 147 { 148 if (count != 0) 149 { 150 ++this.storage; 151 } 152 } 153 154 /** 155 * Decreases the reference counter by one. 156 * 157 * If the counter reaches 0, destroys the owned object. 158 */ 159 ~this() 160 { 161 if (this.storage !is null && !(this.storage > 0 && --this.storage)) 162 { 163 deleter(this.storage, allocator); 164 } 165 } 166 167 /** 168 * Takes ownership over $(D_PARAM rhs). Initializes this 169 * $(D_PSYMBOL RefCounted) if needed. 170 * 171 * If it is the last reference of the previously owned object, 172 * it will be destroyed. 173 * 174 * To reset $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null). 175 * 176 * If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will 177 * be used. If you need a different allocator, create a new 178 * $(D_PSYMBOL RefCounted) and assign it. 179 * 180 * Params: 181 * rhs = New object. 182 * 183 * Returns: $(D_KEYWORD this). 184 */ 185 ref typeof(this) opAssign(Payload!T rhs) 186 { 187 if (this.storage is null) 188 { 189 this.storage = allocator.make!Storage(); 190 this.deleter = &separateDeleter!(Payload!T); 191 } 192 else if (this.storage > 1) 193 { 194 --this.storage; 195 this.storage = allocator.make!Storage(); 196 this.deleter = &separateDeleter!(Payload!T); 197 } 198 else 199 { 200 finalize(this.storage.payload); 201 this.storage.payload = Payload!T.init; 202 } 203 this.storage.payload = rhs; 204 return this; 205 } 206 207 /// ditto 208 ref typeof(this) opAssign(typeof(null)) 209 { 210 if (this.storage is null) 211 { 212 return this; 213 } 214 else if (this.storage > 1) 215 { 216 --this.storage; 217 } 218 else 219 { 220 deleter(this.storage, allocator); 221 } 222 this.storage = null; 223 224 return this; 225 } 226 227 /// ditto 228 ref typeof(this) opAssign(typeof(this) rhs) 229 { 230 swap(this.allocator_, rhs.allocator_); 231 swap(this.storage, rhs.storage); 232 swap(this.deleter, rhs.deleter); 233 return this; 234 } 235 236 /** 237 * Returns: Reference to the owned object. 238 * 239 * Precondition: $(D_INLINECODE cound > 0). 240 */ 241 inout(Payload!T) get() inout 242 in 243 { 244 assert(count > 0, "Attempted to access an uninitialized reference"); 245 } 246 do 247 { 248 return this.storage.payload; 249 } 250 251 version (D_Ddoc) 252 { 253 /** 254 * Dereferences the pointer. It is defined only for pointers, not for 255 * reference types like classes, that can be accessed directly. 256 * 257 * Params: 258 * op = Operation. 259 * 260 * Returns: Reference to the pointed value. 261 */ 262 ref inout(T) opUnary(string op)() inout 263 if (op == "*"); 264 } 265 else static if (isPointer!(Payload!T)) 266 { 267 ref inout(T) opUnary(string op)() inout 268 if (op == "*") 269 { 270 return *this.storage.payload; 271 } 272 } 273 274 /** 275 * Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal 276 * storage. 277 */ 278 @property bool isInitialized() const 279 { 280 return this.storage !is null; 281 } 282 283 /** 284 * Returns: The number of $(D_PSYMBOL RefCounted) instances that share 285 * ownership over the same pointer (including $(D_KEYWORD this)). 286 * If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`. 287 */ 288 @property size_t count() const 289 { 290 return this.storage is null ? 0 : this.storage.counter; 291 } 292 293 mixin DefaultAllocator; 294 alias get this; 295 } 296 297 /// 298 @nogc @system unittest 299 { 300 auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator); 301 auto val = rc.get(); 302 303 *val = 8; 304 assert(*rc.get == 8); 305 306 val = null; 307 assert(rc.get !is null); 308 assert(*rc.get == 8); 309 310 *rc = 9; 311 assert(*rc.get == 9); 312 } 313 314 /** 315 * Constructs a new object of type $(D_PARAM T) and wraps it in a 316 * $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for 317 * the constructor of $(D_PARAM T). 318 * 319 * This function is more efficient than the using of $(D_PSYMBOL RefCounted) 320 * directly, since it allocates only ones (the internal storage and the 321 * object). 322 * 323 * Params: 324 * T = Type of the constructed object. 325 * A = Types of the arguments to the constructor of $(D_PARAM T). 326 * allocator = Allocator. 327 * args = Constructor arguments of $(D_PARAM T). 328 * 329 * Returns: Newly created $(D_PSYMBOL RefCounted!T). 330 * 331 * Precondition: $(D_INLINECODE allocator !is null) 332 */ 333 RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args) 334 if (!is(T == interface) && !isAbstractClass!T 335 && !isAssociativeArray!T && !isArray!T) 336 in 337 { 338 assert(allocator !is null); 339 } 340 do 341 { 342 auto rc = typeof(return)(allocator); 343 344 const storageSize = alignedSize(stateSize!(RefCounted!T.Storage)); 345 const size = alignedSize(stateSize!T + storageSize); 346 347 auto mem = (() @trusted => allocator.allocate(size))(); 348 if (mem is null) 349 { 350 onOutOfMemoryError(); 351 } 352 scope (failure) 353 { 354 () @trusted { allocator.deallocate(mem); }(); 355 } 356 rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]); 357 rc.storage.payload = emplace!T(mem[storageSize .. $], args); 358 359 rc.deleter = &unifiedDeleter!(Payload!T); 360 return rc; 361 } 362 363 /** 364 * Constructs a new array with $(D_PARAM size) elements and wraps it in a 365 * $(D_PSYMBOL RefCounted). 366 * 367 * Params: 368 * T = Array type. 369 * E = Array element type. 370 * size = Array size. 371 * allocator = Allocator. 372 * 373 * Returns: Newly created $(D_PSYMBOL RefCounted!T). 374 * 375 * Precondition: $(D_INLINECODE allocator !is null 376 * && size <= size_t.max / E.sizeof) 377 */ 378 RefCounted!T refCounted(T : E[], E)(shared Allocator allocator, size_t size) 379 @trusted 380 in 381 { 382 assert(allocator !is null); 383 assert(size <= size_t.max / E.sizeof); 384 } 385 do 386 { 387 return RefCounted!T(allocator.make!T(size), allocator); 388 } 389 390 /// 391 @nogc @system unittest 392 { 393 auto rc = defaultAllocator.refCounted!int(5); 394 assert(rc.count == 1); 395 396 void func(RefCounted!int param) @nogc 397 { 398 if (param.count == 2) 399 { 400 func(param); 401 } 402 else 403 { 404 assert(param.count == 3); 405 } 406 } 407 func(rc); 408 409 assert(rc.count == 1); 410 } 411 412 /** 413 * $(D_PSYMBOL Unique) stores an object that gets destroyed at the end of its scope. 414 * 415 * Params: 416 * T = Value type. 417 */ 418 struct Unique(T) 419 { 420 private Payload!T payload; 421 422 invariant 423 { 424 assert(payload is null || allocator_ !is null); 425 } 426 427 /** 428 * Takes ownership over $(D_PARAM value), setting the counter to 1. 429 * $(D_PARAM value) may be a pointer, an object or a dynamic array. 430 * 431 * Params: 432 * value = Value whose ownership is taken over. 433 * allocator = Allocator used to destroy the $(D_PARAM value) and to 434 * allocate/deallocate internal storage. 435 * 436 * Precondition: $(D_INLINECODE allocator !is null) 437 */ 438 this(Payload!T value, shared Allocator allocator = defaultAllocator) 439 { 440 this(allocator); 441 this.payload = value; 442 } 443 444 /// ditto 445 this(shared Allocator allocator) 446 in 447 { 448 assert(allocator !is null); 449 } 450 do 451 { 452 this.allocator_ = allocator; 453 } 454 455 /** 456 * $(D_PSYMBOL Unique) is noncopyable. 457 */ 458 @disable this(this); 459 460 /** 461 * Destroys the owned object. 462 */ 463 ~this() 464 { 465 allocator.dispose(this.payload); 466 } 467 468 /** 469 * Initialized this $(D_PARAM Unique) and takes ownership over 470 * $(D_PARAM rhs). 471 * 472 * To reset $(D_PSYMBOL Unique) assign $(D_KEYWORD null). 473 * 474 * If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will 475 * be used. If you need a different allocator, create a new 476 * $(D_PSYMBOL Unique) and assign it. 477 * 478 * Params: 479 * rhs = New object. 480 * 481 * Returns: $(D_KEYWORD this). 482 */ 483 ref typeof(this) opAssign(Payload!T rhs) 484 { 485 allocator.dispose(this.payload); 486 this.payload = rhs; 487 return this; 488 } 489 490 /// ditto 491 ref typeof(this) opAssign(typeof(null)) 492 { 493 allocator.dispose(this.payload); 494 return this; 495 } 496 497 /// ditto 498 ref typeof(this) opAssign(typeof(this) rhs) 499 { 500 swap(this.allocator_, rhs.allocator_); 501 swap(this.payload, rhs.payload); 502 503 return this; 504 } 505 506 /// 507 @nogc nothrow pure @system unittest 508 { 509 auto rc = defaultAllocator.unique!int(5); 510 rc = defaultAllocator.make!int(7); 511 assert(*rc == 7); 512 } 513 514 /** 515 * Returns: Reference to the owned object. 516 */ 517 inout(Payload!T) get() inout 518 { 519 return this.payload; 520 } 521 522 version (D_Ddoc) 523 { 524 /** 525 * Dereferences the pointer. It is defined only for pointers, not for 526 * reference types like classes, that can be accessed directly. 527 * 528 * Params: 529 * op = Operation. 530 * 531 * Returns: Reference to the pointed value. 532 */ 533 ref inout(T) opUnary(string op)() inout 534 if (op == "*"); 535 } 536 else static if (isPointer!(Payload!T)) 537 { 538 ref inout(T) opUnary(string op)() inout 539 if (op == "*") 540 { 541 return *this.payload; 542 } 543 } 544 545 /** 546 * Returns: Whether this $(D_PSYMBOL Unique) holds some value. 547 */ 548 @property bool isInitialized() const 549 { 550 return this.payload !is null; 551 } 552 553 /// 554 @nogc nothrow pure @system unittest 555 { 556 Unique!int u; 557 assert(!u.isInitialized); 558 } 559 560 /** 561 * Sets the internal pointer to $(D_KEYWORD). The allocator isn't changed. 562 * 563 * Returns: Reference to the owned object. 564 */ 565 Payload!T release() 566 { 567 auto payload = this.payload; 568 this.payload = null; 569 return payload; 570 } 571 572 /// 573 @nogc nothrow pure @system unittest 574 { 575 auto u = defaultAllocator.unique!int(5); 576 assert(u.isInitialized); 577 578 auto i = u.release(); 579 assert(*i == 5); 580 assert(!u.isInitialized); 581 } 582 583 mixin DefaultAllocator; 584 alias get this; 585 } 586 587 /// 588 @nogc nothrow pure @system unittest 589 { 590 auto p = defaultAllocator.make!int(5); 591 auto s = Unique!int(p, defaultAllocator); 592 assert(*s == 5); 593 } 594 595 /// 596 @nogc nothrow @system unittest 597 { 598 static bool destroyed; 599 600 static struct F 601 { 602 ~this() @nogc nothrow @safe 603 { 604 destroyed = true; 605 } 606 } 607 { 608 auto s = Unique!F(defaultAllocator.make!F(), defaultAllocator); 609 } 610 assert(destroyed); 611 } 612 613 /** 614 * Constructs a new object of type $(D_PARAM T) and wraps it in a 615 * $(D_PSYMBOL Unique) using $(D_PARAM args) as the parameter list for 616 * the constructor of $(D_PARAM T). 617 * 618 * Params: 619 * T = Type of the constructed object. 620 * A = Types of the arguments to the constructor of $(D_PARAM T). 621 * allocator = Allocator. 622 * args = Constructor arguments of $(D_PARAM T). 623 * 624 * Returns: Newly created $(D_PSYMBOL Unique!T). 625 * 626 * Precondition: $(D_INLINECODE allocator !is null) 627 */ 628 Unique!T unique(T, A...)(shared Allocator allocator, auto ref A args) 629 if (!is(T == interface) && !isAbstractClass!T 630 && !isAssociativeArray!T && !isArray!T) 631 in 632 { 633 assert(allocator !is null); 634 } 635 do 636 { 637 auto payload = allocator.make!(T, A)(args); 638 return Unique!T(payload, allocator); 639 } 640 641 /** 642 * Constructs a new array with $(D_PARAM size) elements and wraps it in a 643 * $(D_PSYMBOL Unique). 644 * 645 * Params: 646 * T = Array type. 647 * E = Array element type. 648 * size = Array size. 649 * allocator = Allocator. 650 * 651 * Returns: Newly created $(D_PSYMBOL Unique!T). 652 * 653 * Precondition: $(D_INLINECODE allocator !is null 654 * && size <= size_t.max / E.sizeof) 655 */ 656 Unique!T unique(T : E[], E)(shared Allocator allocator, size_t size) 657 @trusted 658 in 659 { 660 assert(allocator !is null); 661 assert(size <= size_t.max / E.sizeof); 662 } 663 do 664 { 665 auto payload = allocator.resize!E(null, size); 666 return Unique!T(payload, allocator); 667 }