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