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 4
        this(Args...)(auto ref Args args) {
51 4
            this.makeObject!(supportGC, args)();
52
        }
53

54
    } else {
55

56
        /**
57
           Non-singleton allocator, must be passed in
58
         */
59 4
        this(Args...)(Allocator allocator, auto ref Args args) {
60 4
            _allocator = allocator;
61 4
            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 4
                typeof(return) ret;
75 4
                ret.makeObject!(supportGC)();
76 4
                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 4
    this(T)(Unique!(T, Allocator) other) if(is(T: Type)) {
90 4
        moveFrom(other);
91
    }
92

93
    ///
94
    @disable this(this);
95

96
    ///
97
    ~this() {
98 4
        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 4
        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 4
        Unique u;
118 4
        move(this, u);
119 4
        assert(_object is null);
120 4
        return u;
121
    }
122

123
    /// release ownership
124
    package Pointer release() {
125 4
        auto ret = _object;
126 4
        _object = null;
127 4
        return ret;
128
    }
129

130
    ///
131
    package Allocator allocator() {
132 4
        return _allocator;
133
    }
134

135
    /**
136
       "Truthiness" cast
137
     */
138
    bool opCast(T)() const if(is(T == bool)) {
139 4
        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 4
        deleteObject;
145 4
        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 4
            assert(_object is null || _allocator !is null);
169

170 4
        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 4
            () @trusted {
174 4
                auto repr = (cast(void*)_object)[0..__traits(classInstanceSize, Type)];
175 4
                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 4
        _object = other._object;
186 4
        other._object = null;
187

188
        static if(!isGlobal) {
189
            import std.algorithm: move;
190 4
            _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 4
    auto s = Unique!S.construct();
203
    static assert(is(typeof(s) == Unique!S));
204 4
    assert(s._object !is null);
205 4
    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 4
        auto allocator = TestAllocator();
217 4
        auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 42);
218 4
        ptr.release;
219 4
        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 4
        return;
225
    }
226

227 0
    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 4
        u._object = () @trusted { return u._allocator.make!Type(forward!args); }();
240

241
        static if (is(Type == class)) {
242 4
            () @trusted {
243 4
                auto repr = (cast(void*)u._object)[0..__traits(classInstanceSize, Type)];
244 4
                if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) {
245 0
                    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 4
                    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
}

Read our documentation on viewing source code .

Loading