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 }