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