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