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