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 }