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 Borrow the owned pointer. 93 Can be @safe with DIP1000 and if used in a scope fashion. 94 */ 95 auto borrow() inout { 96 return _object; 97 } 98 99 alias get = borrow; // backwards compatibility 100 101 /** 102 Releases ownership and transfers it to the returned 103 Unique object. 104 */ 105 Unique unique() { 106 import std.algorithm: move; 107 Unique u; 108 move(this, u); 109 assert(_object is null); 110 return u; 111 } 112 113 /// release ownership 114 package Pointer release() { 115 auto ret = _object; 116 _object = null; 117 return ret; 118 } 119 120 /// 121 package Allocator allocator() { 122 return _allocator; 123 } 124 125 /** 126 "Truthiness" cast 127 */ 128 bool opCast(T)() const if(is(T == bool)) { 129 return _object !is null; 130 } 131 132 /// Move from another smart pointer 133 void opAssign(T)(Unique!(T, Allocator) other) if(is(T: Type)) { 134 deleteObject; 135 moveFrom(other); 136 } 137 138 mixin Proxy!_object; 139 140 private: 141 142 Pointer _object; 143 144 static if(isSingleton) 145 alias _allocator = Allocator.instance; 146 else static if(isTheAllocator) 147 alias _allocator = theAllocator; 148 else 149 Allocator _allocator; 150 151 void deleteObject() @safe { 152 import automem.allocator: dispose; 153 import std.traits: isPointer; 154 import std.traits : hasIndirections; 155 import core.memory : GC; 156 157 static if(isPointer!Allocator) 158 assert(_object is null || _allocator !is null); 159 160 if(_object !is null) () @trusted { _allocator.dispose(_object); }(); 161 static if (is(Type == class)) { 162 // need to watch the monitor pointer even if supportGC is false. 163 () @trusted { 164 auto repr = (cast(void*)_object)[0..__traits(classInstanceSize, Type)]; 165 GC.removeRange(&repr[(void*).sizeof]); 166 }(); 167 } else static if (supportGC && hasIndirections!Type) { 168 () @trusted { 169 GC.removeRange(_object); 170 }(); 171 } 172 } 173 174 void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) { 175 _object = other._object; 176 other._object = null; 177 178 static if(!isGlobal) { 179 import std.algorithm: move; 180 _allocator = other._allocator.move; 181 } 182 } 183 } 184 185 private template makeObject(Flag!"supportGC" supportGC, args...) 186 { 187 void makeObject(Type,A)(ref Unique!(Type, A) u) { 188 import stdx.allocator: make; 189 import std.functional : forward; 190 import std.traits : hasIndirections; 191 import core.memory : GC; 192 193 u._object = u._allocator.make!Type(forward!args); 194 195 static if (is(Type == class)) { 196 () @trusted { 197 auto repr = (cast(void*)u._object)[0..__traits(classInstanceSize, Type)]; 198 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) { 199 GC.addRange(&repr[(void*).sizeof], 200 __traits(classInstanceSize, Type) - (void*).sizeof); 201 } else { 202 // need to watch the monitor pointer even if supportGC is false. 203 GC.addRange(&repr[(void*).sizeof], (void*).sizeof); 204 } 205 }(); 206 } else static if (supportGC && hasIndirections!Type) { 207 () @trusted { 208 GC.addRange(u._object, Type.sizeof); 209 }(); 210 } 211 } 212 } 213 214 215 @("Construct Unique using global allocator for struct with zero-args ctor") 216 @system unittest { 217 struct S { 218 private ulong zeroArgsCtorTest = 3; 219 } 220 auto s = Unique!S.construct(); 221 static assert(is(typeof(s) == Unique!S)); 222 assert(s._object !is null); 223 assert(s.zeroArgsCtorTest == 3); 224 } 225 226 227 @("release") 228 @system unittest { 229 import stdx.allocator: dispose; 230 import core.exception: AssertError; 231 232 try { 233 auto allocator = TestAllocator(); 234 auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 42); 235 ptr.release; 236 Struct.numStructs.shouldEqual(0); 237 } catch(AssertError _) { // TestAllocator should throw due to memory leak 238 return; 239 } 240 241 assert(0); // should throw above 242 }