1 /** 2 A reference-counted smart pointer. 3 */ 4 module automem.ref_counted; 5 6 import automem.traits: isAllocator; 7 import automem.unique: Unique; 8 import stdx.allocator: theAllocator, processAllocator; 9 import std.typecons: Flag; 10 11 12 /** 13 A reference-counted smart pointer similar to C++'s std::shared_ptr. 14 */ 15 struct RefCounted(Type, 16 Allocator = typeof(theAllocator), 17 Flag!"supportGC" supportGC = Flag!"supportGC".yes) 18 if(isAllocator!Allocator) 19 { 20 21 import std.traits: hasMember; 22 23 enum isSingleton = hasMember!(Allocator, "instance"); 24 enum isTheAllocator = is(Allocator == typeof(theAllocator)); 25 enum isGlobal = isSingleton || isTheAllocator; 26 27 static if(isGlobal) 28 /** 29 The allocator is a singleton, so no need to pass it in to the 30 constructor 31 */ 32 this(Args...)(auto ref Args args) { 33 this.makeObject!args(); 34 } 35 else 36 /** 37 Non-singleton allocator, must be passed in 38 */ 39 this(Args...)(Allocator allocator, auto ref Args args) { 40 _allocator = allocator; 41 this.makeObject!args(); 42 } 43 44 static if(isGlobal) 45 /** 46 Factory method so can construct with zero args. 47 */ 48 static typeof(this) construct(Args...)(auto ref Args args) { 49 static if (Args.length != 0) 50 return typeof(return)(args); 51 else { 52 typeof(return) ret; 53 ret.makeObject!()(); 54 return ret; 55 } 56 } 57 else 58 /** 59 Factory method. Not necessary with non-global allocator 60 but included for symmetry. 61 */ 62 static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) { 63 return typeof(return)(allocator, args); 64 } 65 66 /// 67 this(this) { 68 assert(_impl !is null); 69 inc; 70 } 71 72 /// 73 ~this() { 74 release; 75 } 76 77 /** 78 Assign to an lvalue RefCounted 79 */ 80 void opAssign(ref RefCounted other) { 81 82 if (_impl == other._impl) return; 83 84 if(_impl !is null) release; 85 86 static if(!isGlobal) 87 _allocator = other._allocator; 88 89 _impl = other._impl; 90 91 if(_impl !is null) inc; 92 } 93 94 /** 95 Assign to an rvalue RefCounted 96 */ 97 void opAssign(RefCounted other) { 98 import std.algorithm: swap; 99 swap(_impl, other._impl); 100 static if(!isGlobal) 101 swap(_allocator, other._allocator); 102 } 103 104 /** 105 Dereference the smart pointer and yield a reference 106 to the contained type. 107 */ 108 ref auto opUnary(string s)() inout if (s == "*") { 109 return _impl._get; 110 } 111 112 /** 113 Prevent opSlice and opIndex from being hidden by Impl*. 114 This comment is deliberately not DDOC. 115 */ 116 auto ref opSlice(A...)(auto ref A args) 117 if (__traits(compiles, Type.init.opSlice(args))) 118 { 119 return _impl._get.opSlice(args); 120 } 121 /// ditto 122 auto ref opIndex(A...)(auto ref A args) 123 if (__traits(compiles, Type.init.opIndex(args))) 124 { 125 return _impl._get.opIndex(args); 126 } 127 /// ditto 128 auto ref opIndexAssign(A...)(auto ref A args) 129 if (__traits(compiles, Type.init.opIndexAssign(args))) 130 { 131 return _impl._get.opIndexAssign(args); 132 } 133 134 alias _impl this; 135 136 private: 137 138 static struct Impl { 139 140 static if(is(Type == class)) { 141 142 align ((void*).alignof) 143 void[__traits(classInstanceSize, Type)] _rawMemory; 144 145 } else 146 Type _object; 147 148 static if(is(Type == shared)) 149 shared size_t _count; 150 else 151 size_t _count; 152 153 static if (is(Type == class)) { 154 inout(Type) _get() inout { 155 return cast(inout(Type))&_rawMemory[0]; 156 } 157 158 inout(shared(Type)) _get() inout shared { 159 return cast(inout(shared(Type)))&_rawMemory[0]; 160 } 161 } else { 162 ref inout(Type) _get() inout { 163 return _object; 164 } 165 166 ref inout(shared(Type)) _get() inout shared { 167 return _object; 168 } 169 } 170 171 alias _get this; 172 } 173 174 static if(isSingleton) 175 alias _allocator = Allocator.instance; 176 else static if(isTheAllocator) { 177 static if (is(Type == shared)) 178 // 'processAllocator' should be used for allocating 179 // memory shared across threads 180 alias _allocator = processAllocator; 181 else 182 alias _allocator = theAllocator; 183 } 184 else 185 Allocator _allocator; 186 187 static if(is(Type == shared)) 188 alias ImplType = shared Impl; 189 else 190 alias ImplType = Impl; 191 192 public ImplType* _impl; // public or alias this doesn't work 193 194 void allocateImpl() { 195 import stdx.allocator: make; 196 import std.traits: hasIndirections; 197 198 _impl = cast(typeof(_impl))_allocator.allocate(Impl.sizeof); 199 _impl._count= 1; 200 201 static if (is(Type == class)) { 202 // class representation: 203 // void* classInfoPtr 204 // void* monitorPtr 205 // [] interfaces 206 // T... members 207 import core.memory: GC; 208 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) 209 // members have pointers: we have to watch the monitor 210 // and all members; skip the classInfoPtr 211 GC.addRange(&_impl._rawMemory[(void*).sizeof], 212 __traits(classInstanceSize, Type) - (void*).sizeof); 213 else 214 // representation doesn't have pointers, just watch the 215 // monitor pointer; skip the classInfoPtr 216 // need to watch the monitor pointer even if supportGC is false. 217 GC.addRange(&_impl._rawMemory[(void*).sizeof], (void*).sizeof); 218 } else static if (supportGC && hasIndirections!Type) { 219 import core.memory: GC; 220 GC.addRange(&_impl._object, Type.sizeof); 221 } 222 } 223 224 void release() { 225 import std.traits : hasIndirections; 226 import core.memory : GC; 227 import automem.utils : destruct; 228 if(_impl is null) return; 229 assert(_impl._count > 0, "Trying to release a RefCounted but ref count is 0 or less"); 230 231 dec; 232 233 if(_impl._count == 0) { 234 destruct(_impl._get); 235 static if (is(Type == class)) { 236 // need to watch the monitor pointer even if supportGC is false. 237 GC.removeRange(&_impl._rawMemory[(void*).sizeof]); 238 } else static if (supportGC && hasIndirections!Type) { 239 GC.removeRange(&_impl._object); 240 } 241 auto mem = cast(void*)_impl; 242 _allocator.deallocate(() @trusted { return mem[0 .. Impl.sizeof]; }()); 243 } 244 } 245 246 void inc() { 247 static if(is(Type == shared)) { 248 import core.atomic: atomicOp; 249 _impl._count.atomicOp!"+="(1); 250 } else 251 ++_impl._count; 252 253 } 254 255 void dec() { 256 static if(is(Type == shared)) { 257 import core.atomic: atomicOp; 258 _impl._count.atomicOp!"-="(1); 259 } else 260 --_impl._count; 261 } 262 263 } 264 265 private template makeObject(args...) 266 { 267 void makeObject(Type, A)(ref RefCounted!(Type, A) rc) @trusted { 268 import std.conv: emplace; 269 import std.functional : forward; 270 271 rc.allocateImpl; 272 273 static if(is(Type == class)) 274 emplace!Type(rc._impl._rawMemory, forward!args); 275 else 276 emplace(&rc._impl._object, forward!args); 277 } 278 } 279 280 281 282 auto refCounted(Type, Allocator)(Unique!(Type, Allocator) ptr) { 283 284 RefCounted!(Type, Allocator) ret; 285 286 static if(!ptr.isGlobal) 287 ret._allocator = ptr.allocator; 288 289 ret.allocateImpl; 290 *ret = *ptr; 291 292 return ret; 293 }