1 /**
2    A unique pointer.
3  */
4 module automem.unique;
5 
6 import automem.traits: isAllocator;
7 import std.experimental.allocator: theAllocator;
8 import std.typecons: Flag;
9 
10 version(AutomemTesting) {
11     import ut;
12     mixin TestUtils;
13 }
14 
15 version (D_BetterC)
16     enum gcExists = false;
17 else
18     enum gcExists = true;
19 
20 /**
21    A unique pointer similar to C++'s std::unique_ptr.
22  */
23 struct Unique(
24     UniqueType,
25     Allocator = typeof(theAllocator()),
26     Flag!"supportGC" supportGC = gcExists ? Flag!"supportGC".yes : Flag!"supportGC".no
27 )
28     if(isAllocator!Allocator)
29 {
30 
31     import std.traits: hasMember;
32     import std.typecons: Proxy;
33 
34     enum isSingleton = hasMember!(Allocator, "instance");
35     enum isTheAllocator = is(Allocator == typeof(theAllocator));
36     enum isGlobal = isSingleton || isTheAllocator;
37 
38     alias Type = UniqueType;
39 
40     static if(is(Type == class))
41         alias Pointer = Type;
42     else
43         alias Pointer = Type*;
44 
45     static if(isGlobal) {
46 
47         /**
48            The allocator is global, so no need to pass it in to the constructor
49         */
50         this(Args...)(auto ref Args args) {
51             this.makeObject!(supportGC, args)();
52         }
53 
54     } else {
55 
56         /**
57            Non-singleton allocator, must be passed in
58          */
59         this(Args...)(Allocator allocator, auto ref Args args) {
60             _allocator = allocator;
61             this.makeObject!(supportGC, args)();
62         }
63     }
64 
65 
66     static if(isGlobal)
67         /**
68             Factory method so can construct with zero args.
69         */
70         static typeof(this) construct(Args...)(auto ref Args args) {
71             static if (Args.length != 0)
72                 return typeof(return)(args);
73             else {
74                 typeof(return) ret;
75                 ret.makeObject!(supportGC)();
76                 return ret;
77             }
78         }
79     else
80         /**
81             Factory method. Not necessary with non-global allocator
82             but included for symmetry.
83         */
84         static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) {
85             return typeof(return)(allocator, args);
86         }
87 
88     ///
89     this(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
90         moveFrom(other);
91     }
92 
93     ///
94     @disable this(this);
95 
96     ///
97     ~this() {
98         deleteObject;
99     }
100 
101     /**
102        Borrow the owned pointer.
103        Can be @safe with DIP1000 and if used in a scope fashion.
104      */
105     auto borrow() inout {
106         return _object;
107     }
108 
109     alias get = borrow; // backwards compatibility
110 
111     /**
112        Releases ownership and transfers it to the returned
113        Unique object.
114      */
115     Unique unique() {
116         import std.algorithm: move;
117         Unique u;
118         move(this, u);
119         assert(_object is null);
120         return u;
121     }
122 
123     /// release ownership
124     package Pointer release() {
125         auto ret = _object;
126         _object = null;
127         return ret;
128     }
129 
130     ///
131     package Allocator allocator() {
132         return _allocator;
133     }
134 
135     /**
136        "Truthiness" cast
137      */
138     bool opCast(T)() const if(is(T == bool)) {
139         return _object !is null;
140     }
141 
142     /// Move from another smart pointer
143     void opAssign(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
144         deleteObject;
145         moveFrom(other);
146     }
147 
148     mixin Proxy!_object;
149 
150 private:
151 
152     Pointer _object;
153 
154     static if(isSingleton)
155         alias _allocator = Allocator.instance;
156     else static if(isTheAllocator)
157         alias _allocator = theAllocator;
158     else
159         Allocator _allocator;
160 
161     void deleteObject() @safe {
162         import automem.allocator: dispose;
163         import std.traits: isPointer;
164         import std.traits : hasIndirections;
165         import core.memory : GC;
166 
167         static if(isPointer!Allocator)
168             assert(_object is null || _allocator !is null);
169 
170         if(_object !is null) () @trusted { _allocator.dispose(_object); }();
171         static if (is(Type == class)) {
172             // need to watch the monitor pointer even if supportGC is false.
173             () @trusted {
174                 auto repr = (cast(void*)_object)[0..__traits(classInstanceSize, Type)];
175                 GC.removeRange(&repr[(void*).sizeof]);
176             }();
177         } else static if (supportGC && hasIndirections!Type) {
178             () @trusted {
179                 GC.removeRange(_object);
180             }();
181         }
182     }
183 
184     void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) {
185         _object = other._object;
186         other._object = null;
187 
188         static if(!isGlobal) {
189             import std.algorithm: move;
190             _allocator = other._allocator.move;
191         }
192     }
193 }
194 
195 
196 ///
197 @("Construct Unique using global allocator for struct with zero-args ctor")
198 @system unittest {
199     struct S {
200         private ulong zeroArgsCtorTest = 3;
201     }
202     auto s = Unique!S.construct();
203     static assert(is(typeof(s) == Unique!S));
204     assert(s._object !is null);
205     assert(s.zeroArgsCtorTest == 3);
206 }
207 
208 
209 ///
210 @("release")
211 @system unittest {
212     import std.experimental.allocator: dispose;
213     import core.exception: AssertError;
214 
215     try {
216         auto allocator = TestAllocator();
217         auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 42);
218         ptr.release;
219         assert(Struct.numStructs == 1);
220     } catch(AssertError e) { // TestAllocator should throw due to memory leak
221         version(unitThreadedLight) {}
222         else
223             "Memory leak in TestAllocator".should.be in e.msg;
224         return;
225     }
226 
227     assert(0); // should throw above
228 }
229 
230 
231 private template makeObject(Flag!"supportGC" supportGC, args...)
232 {
233     void makeObject(Type,A)(ref Unique!(Type, A) u) {
234         import std.experimental.allocator: make;
235         import std.functional : forward;
236         import std.traits : hasIndirections;
237         import core.memory : GC;
238 
239         u._object = () @trusted { return u._allocator.make!Type(forward!args); }();
240 
241         static if (is(Type == class)) {
242             () @trusted {
243                 auto repr = (cast(void*)u._object)[0..__traits(classInstanceSize, Type)];
244                 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) {
245                     GC.addRange(&repr[(void*).sizeof],
246                             __traits(classInstanceSize, Type) - (void*).sizeof);
247                 } else {
248                     // need to watch the monitor pointer even if supportGC is false.
249                     GC.addRange(&repr[(void*).sizeof], (void*).sizeof);
250                 }
251             }();
252         } else static if (supportGC && hasIndirections!Type) {
253             () @trusted {
254                 GC.addRange(u._object, Type.sizeof);
255             }();
256         }
257     }
258 }