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) || is(Type == interface)) 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 this(Args...)(auto ref Args args) { 51 this.makeObject!(supportGC, args)(); 52 } 53 54 } else { 55 56 /** 57 Non-singleton allocator, must be passed in 58 */ 59 this(Args...)(Allocator allocator, auto ref Args args) { 60 _allocator = allocator; 61 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 typeof(return) ret; 75 ret.makeObject!(supportGC)(); 76 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 this(T)(Unique!(T, Allocator) other) if(is(T: Type)) { 90 moveFrom(other); 91 } 92 93 /// 94 @disable this(this); 95 96 /// 97 ~this() { 98 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 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 Unique u; 118 move(this, u); 119 assert(_object is null); 120 return u; 121 } 122 123 /// release ownership 124 package Pointer release() { 125 auto ret = _object; 126 _object = null; 127 return ret; 128 } 129 130 /// 131 package Allocator allocator() { 132 return _allocator; 133 } 134 135 /** 136 "Truthiness" cast 137 */ 138 bool opCast(T)() const if(is(T == bool)) { 139 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 deleteObject; 145 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 assert(_object is null || _allocator !is null); 169 170 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 () @trusted { 174 auto repr = (cast(void*)_object)[0..__traits(classInstanceSize, Type)]; 175 GC.removeRange(&repr[(void*).sizeof]); 176 }(); 177 } else static if (supportGC && hasIndirections!Type && !is(Type == interface)) { 178 () @trusted { 179 GC.removeRange(_object); 180 }(); 181 } 182 } 183 184 void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) { 185 _object = other._object; 186 other._object = null; 187 188 static if(!isGlobal) { 189 import std.algorithm: move; 190 _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 auto s = Unique!S.construct(); 203 static assert(is(typeof(s) == Unique!S)); 204 assert(s._object !is null); 205 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 auto allocator = TestAllocator(); 217 auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 42); 218 ptr.release; 219 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 return; 225 } 226 227 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 u._object = () @trusted { return u._allocator.make!Type(forward!args); }(); 240 241 static if (is(Type == class) || is(Type == interface)) { 242 () @trusted { 243 auto repr = (cast(void*)u._object)[0..__traits(classInstanceSize, Type)]; 244 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) { 245 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 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 }