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 }