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 }