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        Borrow the owned pointer.
93        Can be @safe with DIP1000 and if used in a scope fashion.
94      */
95     auto borrow() inout {
96         return _object;
97     }
98 
99     alias get = borrow; // backwards compatibility
100 
101     /**
102        Releases ownership and transfers it to the returned
103        Unique object.
104      */
105     Unique unique() {
106         import std.algorithm: move;
107         Unique u;
108         move(this, u);
109         assert(_object is null);
110         return u;
111     }
112 
113     /// release ownership
114     package Pointer release() {
115         auto ret = _object;
116         _object = null;
117         return ret;
118     }
119 
120     ///
121     package Allocator allocator() {
122         return _allocator;
123     }
124 
125     /**
126        "Truthiness" cast
127      */
128     bool opCast(T)() const if(is(T == bool)) {
129         return _object !is null;
130     }
131 
132     /// Move from another smart pointer
133     void opAssign(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
134         deleteObject;
135         moveFrom(other);
136     }
137 
138     mixin Proxy!_object;
139 
140 private:
141 
142     Pointer _object;
143 
144     static if(isSingleton)
145         alias _allocator = Allocator.instance;
146     else static if(isTheAllocator)
147         alias _allocator = theAllocator;
148     else
149         Allocator _allocator;
150 
151     void deleteObject() @safe {
152         import automem.allocator: dispose;
153         import std.traits: isPointer;
154         import std.traits : hasIndirections;
155         import core.memory : GC;
156 
157         static if(isPointer!Allocator)
158             assert(_object is null || _allocator !is null);
159 
160         if(_object !is null) () @trusted { _allocator.dispose(_object); }();
161         static if (is(Type == class)) {
162             // need to watch the monitor pointer even if supportGC is false.
163             () @trusted {
164                 auto repr = (cast(void*)_object)[0..__traits(classInstanceSize, Type)];
165                 GC.removeRange(&repr[(void*).sizeof]);
166             }();
167         } else static if (supportGC && hasIndirections!Type) {
168             () @trusted {
169                 GC.removeRange(_object);
170             }();
171         }
172     }
173 
174     void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) {
175         _object = other._object;
176         other._object = null;
177 
178         static if(!isGlobal) {
179             import std.algorithm: move;
180             _allocator = other._allocator.move;
181         }
182     }
183 }
184 
185 private template makeObject(Flag!"supportGC" supportGC, args...)
186 {
187     void makeObject(Type,A)(ref Unique!(Type, A) u) {
188         import stdx.allocator: make;
189         import std.functional : forward;
190         import std.traits : hasIndirections;
191         import core.memory : GC;
192 
193         u._object = u._allocator.make!Type(forward!args);
194 
195         static if (is(Type == class)) {
196             () @trusted {
197                 auto repr = (cast(void*)u._object)[0..__traits(classInstanceSize, Type)];
198                 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) {
199                     GC.addRange(&repr[(void*).sizeof],
200                             __traits(classInstanceSize, Type) - (void*).sizeof);
201                 } else {
202                     // need to watch the monitor pointer even if supportGC is false.
203                     GC.addRange(&repr[(void*).sizeof], (void*).sizeof);
204                 }
205             }();
206         } else static if (supportGC && hasIndirections!Type) {
207             () @trusted {
208                 GC.addRange(u._object, Type.sizeof);
209             }();
210         }
211     }
212 }
213 
214 
215 @("Construct Unique using global allocator for struct with zero-args ctor")
216 @system unittest {
217     struct S {
218         private ulong zeroArgsCtorTest = 3;
219     }
220     auto s = Unique!S.construct();
221     static assert(is(typeof(s) == Unique!S));
222     assert(s._object !is null);
223     assert(s.zeroArgsCtorTest == 3);
224 }
225 
226 
227 @("release")
228 @system unittest {
229     import stdx.allocator: dispose;
230     import core.exception: AssertError;
231 
232     try {
233         auto allocator = TestAllocator();
234         auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 42);
235         ptr.release;
236         Struct.numStructs.shouldEqual(0);
237     } catch(AssertError _) { // TestAllocator should throw due to memory leak
238         return;
239     }
240 
241     assert(0); // should throw above
242 }