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