1 /*
2 MIT License
3 Copyright (c) 2017 Boris Barboris
4 */
5 
6 module daii.unique;
7 
8 import std.experimental.allocator: make, dispose;
9 import std.experimental.allocator.mallocator: Mallocator;
10 import std.functional: forward;
11 import std.traits: isArray, isAbstractClass, isAssignable;
12 
13 import daii.refcounted;
14 import daii.utils;
15 
16 
17 template AllocationContext(Allocator = Mallocator, bool Atomic = true)
18     if (isAllocator!Allocator)
19 {
20     /// Unique memory owner, holds one instance of type T. Don't use it to hold
21     /// built-in arrays, use custom array type instead. Deallocates in destructor.
22     struct Unique(T)
23         if (!isArray!T)
24     {
25         enum HoldsAllocator = !isStaticAllocator!Allocator;
26 
27         static if (isClassOrIface!T)
28         {
29             alias PtrT = T;
30             @property inout(T) v() inout @nogc @safe
31             {
32                 assert(valid);
33                 return ptr;
34             }
35         }
36         else
37         {
38             alias PtrT = T*;
39             @property ref inout(T) v() inout @nogc @safe
40             {
41                 assert(valid);
42                 return *ptr;
43             }
44         }
45 
46         private PtrT ptr;
47 
48         @property bool valid() const @safe @nogc { return ptr !is null; }
49 
50         @disable this();
51 
52         static if (HoldsAllocator)
53             private Allocator allocator;
54 
55         // don't generate constructors for abstract types
56         static if (!(is(T == interface) || isAbstractClass!(T)))
57         {
58             static if (!HoldsAllocator)
59             {
60                 private this(PtrT ptr) @safe @nogc
61                 {
62                     this.ptr = ptr;
63                 }
64 
65                 // factory function for types with parameterless constructors
66                 static Unique!T make(Args...)(auto ref Args args)
67                 {
68                     auto ptr = Allocator.instance.make!(T)(forward!args);
69                     auto uq = Unique!(T)(ptr);
70                     assert(uq.valid);
71                     return uq;
72                 }
73             }
74             else
75             {
76                 private this(PtrT ptr, Allocator alloc) @safe @nogc
77                 {
78                     this.ptr = ptr;
79                     this.allocator = alloc;
80                 }
81 
82                 static Unique!T make(Args...)(auto ref Allocator alloc,
83                     auto ref Args args)
84                 {
85                     auto ptr = alloc.make!T(forward!args);
86                     auto uq = Unique!(T)(ptr, alloc);
87                     assert(uq.valid);
88                     return uq;
89                 }
90             }
91         }
92 
93         // Templated copy constructor for polymorphic upcasting.
94         this(DT)(scope auto ref Unique!DT rhs)
95         {
96             static if (isClassOrIface!T)
97                 static assert(isAssignable!(T, DT));
98             else
99                 static assert(is(T == DT));
100             assert(rhs.valid);
101             this.ptr = rhs.ptr;
102             rhs.ptr = null;
103             static if (HoldsAllocator)
104                 this.allocator = rhs.allocator;
105         }
106 
107         static if (isClassOrIface!T)
108         {
109             // Polymorphic upcast with ownership transfer
110             Unique!BT to(BT)()
111                 if (isClassOrIface!BT && isAssignable!(BT, T))
112             {
113                 Unique!BT rv = Unique!(BT)(this);
114                 assert(rv.valid);
115                 assert(!valid);
116                 return rv;
117             }
118         }
119 
120         // move ownership to new rvalue Unique
121         Unique!T move()
122         {
123             auto rv = Unique!(T)(this);
124             assert(!valid);
125             return rv;
126         }
127 
128         // move ownership to new refcounted
129         alias SiblingRefCounted =
130             daii.refcounted.AllocationContext!(Allocator, Atomic).RefCounted!(T);
131 
132         SiblingRefCounted toRefCounted()
133         {
134             static if (HoldsAllocator)
135                 auto rc = SiblingRefCounted(ptr, allocator);
136             else
137                 auto rc = SiblingRefCounted(ptr);
138             this.ptr = null;
139             assert(!valid);
140             return rc;
141         }
142 
143         // bread and butter
144         ~this()
145         {
146             destroy();
147         }
148 
149         // destroy the resource (destructor + free memory)
150         void destroy()
151         {
152             if (valid)
153             {
154                 static if (HoldsAllocator)
155                     allocator.dispose(ptr);
156                 else
157                     Allocator.instance.dispose(ptr);
158                 ptr = null;
159             }
160         }
161 
162         // no ownership transfers, use move and construct new uniqueptr
163         @disable this(this);
164         @disable ref Unique!T opAssign(DT)(Unique!DT rhs);
165         @disable ref Unique!T opAssign(DT)(ref Unique!DT rhs);
166     }
167 }
168 
169 private template Unique(T)
170 {
171     alias Unique = AllocationContext!(Mallocator, true).Unique!T;
172 }
173 
174 unittest
175 {
176     auto uq = Unique!int.make(5);
177     assert(uq.valid);
178     assert(uq.v == 5);
179     uq.v = 7;
180     assert(uq.v == 7);
181 }
182 
183 unittest
184 {
185     auto create_uniq()
186     {
187         return Unique!int.make(0);
188     }
189     void consume_uniq(Unique!int u)
190     {
191         assert(u.valid);
192         assert(u.v == 5);
193     }
194     auto u = create_uniq();
195     assert(u.valid);
196     assert(u.v == 0);
197     u.v = 5;
198     consume_uniq(u.move);
199     assert(!u.valid);
200 }
201 
202 unittest
203 {
204     struct TS
205     {
206         int x = -3;
207     }
208     auto u = Unique!TS.make;
209     assert(u.v.x == -3);
210 }
211 
212 unittest
213 {
214     static int count = 0;
215     static int total = 0;
216     class TC
217     {
218         this() { count++; total++; }
219         ~this() { count--; }
220         int j = 3;
221     }
222     auto u1 = Unique!TC.make;
223     assert(count == 1);
224     {
225         auto u2 = Unique!TC.make;
226         assert(count == 2);
227         assert(u2.v !is null);
228         assert(u2.v.j == 3);
229     }
230     assert(count == 1);
231     assert(total == 2);
232 }
233 
234 import std.experimental.allocator.showcase;
235 
236 unittest
237 {
238     static int count = 0;
239     static int total = 0;
240     class TC
241     {
242         this() { count++; total++; }
243         ~this() { count--; }
244         int j = 3;
245     }
246     alias Alloc = StackFront!(4096, Mallocator);
247     Alloc al;
248     alias Uq = AllocationContext!(Alloc*, true).Unique!TC;
249     auto u1 = Uq.make(&al);
250     assert(count == 1);
251     {
252         auto u2 = Uq.make(&al);
253         assert(count == 2);
254         assert(u2.v !is null);
255         assert(u2.v.j == 3);
256     }
257     assert(count == 1);
258     assert(total == 2);
259 }
260 
261 unittest
262 {
263     class A {}
264     class B: A {}
265     Unique!A uq = Unique!B.make();
266     assert(uq.valid);
267 }
268 
269 unittest
270 {
271     static int ades = 0;
272     static int bdes = 0;
273     class A { ~this() {ades++;}}
274     class B: A { ~this(){bdes++;}}
275     {
276         Unique!A aq = Unique!A.make();
277         {
278             Unique!A bq = Unique!B.make();
279             assert(ades == 0);
280             assert(bdes == 0);
281         }
282         assert(ades == 1);
283         assert(bdes == 1);
284         Unique!A cq = aq.move;
285         assert(!aq.valid);
286     }
287     assert(ades == 2);
288     assert(bdes == 1);
289 }
290 
291 unittest
292 {
293     static int ades = 0;
294     static int bdes = 0;
295     class A { ~this() {ades++;}}
296     class B: A { ~this(){bdes++;}}
297     void consume(Unique!A uq)
298     {
299         assert(uq.valid);
300         assert(ades == 0);
301         assert(bdes == 0);
302     }
303     {
304         Unique!B aq = Unique!B.make();
305         consume(aq.to!A);
306         assert(ades == 1);
307         assert(bdes == 1);
308     }
309     assert(ades == 1);
310     assert(bdes == 1);
311 }