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