SharedPtr - multiple declarations

Struct SharedPtr

Implementation of a ref counted pointer with support for aliasing.

struct SharedPtr(_Type, _DestructorType, _ControlType, bool _weakPtr = false)
  
if (isControlBlock!_ControlType && isDestructorType!_DestructorType);

SharedPtr retains shared ownership of an object through a pointer.

Several SharedPtr objects may own the same object.

The object is destroyed and its memory deallocated when either of the following happens:

1. the last remaining SharedPtr owning the object is destroyed.

2. the last remaining SharedPtr owning the object is assigned another pointer via various methods like opAssign and store.

The object is destroyed using delete-expression or a custom deleter that is supplied to SharedPtr during construction.

A SharedPtr can share ownership of an object while storing a pointer to another object. This feature can be used to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(), the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count reaches zero.

A SharedPtr may also own no objects, in which case it is called empty (an empty SharedPtr may have a non-null stored pointer if the aliasing constructor was used to create it).

If template parameter _ControlType is shared then all member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of SharedPtr without additional synchronization even if these instances are copies and share ownership of the same object.

If multiple threads of execution access the same SharedPtr (shared SharedPtr) then only some methods can be called (load, store, exchange, compareExchange, useCount).

Template parameters:

_Type type of managed object

_DestructorType function pointer with attributes of destructor, to get attributes of destructor from type use DestructorType!T. Destructor of type _Type must be compatible with _DestructorType

_ControlType represent type of counter, must by of type ControlBlock. if is shared then ref counting is atomic.

_weakPtr if true then SharedPtr represent weak ptr

Constructors

NameDescription
this (nil) Constructs a SharedPtr without managed object. Same as SharedPtr.init
this (rhs, ) Forward constructor (merge move and copy constructor).
this (rhs, element) Constructs a SharedPtr which shares ownership of the object managed by rhs and pointing to element.
this (rhs) Constructs a SharedPtr which shares ownership of the object managed by rhs.

Properties

NameTypeDescription
element[get] ElementReferenceTypeImpl!(GetElementType!This)Get pointer to managed object of ElementType or reference if ElementType is reference type (class or interface) or dynamic array.
expired[get] boolEquivalent to useCount() == 0 (must be SharedPtr.WeakType).
get[get] inout(SharedPtr.ElementType)Get reference to managed object of ElementType or value if ElementType is reference type (class or interface) or dynamic array.
length[get] size_tReturns length of dynamic array (isDynamicArray!ElementType == true).
ptr[get] ElementPointerTypeImpl!(GetElementType!This)Get pointer to managed object of ElementType or reference if ElementType is reference type (class or interface) or pointer to first dynamic array element.
toHash[get] size_tGenerate hash
useCount[get] ControlType.SharedReturns the number of different SharedPtr instances
weakCount[get] ControlType.WeakReturns the number of different SharedPtr.WeakType instances

Methods

NameDescription
alloc (a, args) Constructs an object of type ElementType and wraps it in a SharedPtr using args as the parameter list for the constructor of ElementType.
alloc (allocator, element, deleter) Constructs a SharedPtr with element as the pointer to the managed object using allocator with state.
alloc (a, n, args) Constructs an object of array type ElementType including its array elements and wraps it in a SharedPtr.
compareExchange (expected, desired) Compares the SharedPtr pointers pointed-to by this and expected.
exchange () Stores the non shared SharedPtr pointer ptr in the shared(SharedPtr) pointed to by this and returns the value formerly pointed-to by this, atomically or with mutex.
load () Returns the non shared SharedPtr pointer pointed-to by shared this.
lock () Creates a new non weak SharedPtr that shares ownership of the managed object (must be SharedPtr.WeakType).
make (args) Constructs an object of type ElementType and wraps it in a SharedPtr using args as the parameter list for the constructor of ElementType.
make (element, deleter) Constructs a SharedPtr with element as the pointer to the managed object.
make (n, args) Constructs an object of array type ElementType including its array elements and wraps it in a SharedPtr.
opAssign (nil) Releases the ownership of the managed object, if any.
opAssign (desired) Shares ownership of the object managed by rhs.
opCast () Checks if this stores a non-null pointer, i.e. whether this != null.
opCast () Support for quelifier cast.
opCastImpl () Cast this to different type To when isSharedPtr!To.
opCmp (elm) Operators <, <=, >, >= for SharedPtr.
opEquals (nil) Operator == and != . Compare pointers.
proxySwap (rhs) Swap this with rhs
weak () Returns weak pointer (must have weak counter).

Aliases

NameDescription
compareExchangeStrong Same as compareExchange.
compareExchangeWeak Same as compareExchange.
ControlType Type of control block.
DestructorType Type of destructor (void function(void*)@attributes).
ElementReferenceType Same as ElementType* or ElementType if is class/interface/slice.
ElementType Type of element managed by SharedPtr.
isLockFree true if shared SharedPtr has lock free operations store, load, exchange, compareExchange, otherwise 'false'
isWeak true if SharedPtr is weak ptr.
opUnary Operator *, same as method 'get'.
SharedType Type of non weak ptr.
store Stores the non shared SharedPtr parameter ptr to this.
WeakType Weak pointer

Alias SharedPtr

Alias to SharedPtr with different order of template parameters

alias SharedPtr(_Type, _ControlType, _DestructorType, bool _weakPtr = false) = SharedPtr!(_Type,_DestructorType,_ControlType,_weakPtr);

Example

static class Foo{
	int i;

	this(int i)pure nothrow @safe @nogc{
		this.i = i;
	}
}

static class Bar : Foo{
	double d;

	this(int i, double d)pure nothrow @safe @nogc{
		super(i);
		this.d = d;
	}
}

static class Zee : Bar{
	bool b;

	this(int i, double d, bool b)pure nothrow @safe @nogc{
		super(i, d);
		this.b = b;
	}

	~this()nothrow @system{
	}
}

///simple:
{
	SharedPtr!long a = SharedPtr!long.make(42);
	assert(a.useCount == 1);

	SharedPtr!(const long) b = a;
	assert(a.useCount == 2);

	SharedPtr!long.WeakType w = a.weak; //or WeakPtr!long
	assert(a.useCount == 2);
	assert(a.weakCount == 1);

	SharedPtr!long c = w.lock;
	assert(a.useCount == 3);
	assert(a.weakCount == 1);

	assert(*c == 42);
	assert(c.get == 42);
}

///polymorphism and aliasing:
{
	///create SharedPtr
	SharedPtr!Foo foo = SharedPtr!Bar.make(42, 3.14);
	SharedPtr!Zee zee = SharedPtr!Zee.make(42, 3.14, false);

	///dynamic cast:
	SharedPtr!Bar bar = dynCast!Bar(foo);
	assert(bar != null);
	assert(foo.useCount == 2);

	///this doesnt work because Foo destructor attributes are more restrictive then Zee's:
	//SharedPtr!Foo x = zee;

	///this does work:
	SharedPtr!(Foo, DestructorType!(Foo, Zee)) x = zee;
	assert(zee.useCount == 2);

	///aliasing (shared ptr `d` share ref counting with `bar`):
	SharedPtr!double d = SharedPtr!double(bar, &bar.get.d);
	assert(d != null);
	assert(*d == 3.14);
	assert(foo.useCount == 3);
}


///multi threading:
{
	///create SharedPtr with atomic ref counting
	SharedPtr!(shared Foo) foo = SharedPtr!(shared Bar).make(42, 3.14);

	///this doesnt work:
	//foo.get.i += 1;

	import core.atomic : atomicFetchAdd;
	atomicFetchAdd(foo.get.i, 1);
	assert(foo.get.i == 43);


	///creating `shared(SharedPtr)`:
	shared SharedPtr!(shared Bar) bar = share(dynCast!Bar(foo));

	///`shared(SharedPtr)` is not lock free but `RcPtr` is lock free.
	static assert(typeof(bar).isLockFree == false);

	///multi thread operations (`load`, `store`, `exchange` and `compareExchange`):
	SharedPtr!(shared Bar) bar2 = bar.load();
	assert(bar2 != null);
	assert(bar2.useCount == 3);

	SharedPtr!(shared Bar) bar3 = bar.exchange(null);
	assert(bar3 != null);
	assert(bar3.useCount == 3);
}

///dynamic array:
{
	import std.algorithm : all, equal;

	SharedPtr!(long[]) a = SharedPtr!(long[]).make(10, -1);
	assert(a.length == 10);
	assert(a.get.length == 10);
	assert(a.get.all!(x => x == -1));

	for(int i = 0; i < a.length; ++i){
		a.get[i] = i;
	}
	assert(a.get[] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

	///aliasing:
	SharedPtr!long a6 = SharedPtr!long(a, &a.get[6]);
	assert(*a6 == a.get[6]);
}

Example

//make SharedPtr object
static struct Foo{
	int i;

	this(int i)pure nothrow @safe @nogc{
		this.i = i;
	}
}

{
	auto foo = SharedPtr!Foo.make(42);
	auto foo2 = SharedPtr!Foo.make!Mallocator(42);  //explicit stateless allocator
}

{
	import std.experimental.allocator : make, dispose;

	static void deleter(long* x)pure nothrow @trusted @nogc{
		Mallocator.instance.dispose(x);
	}
	long* element = Mallocator.instance.make!long;

	auto x = SharedPtr!long.make(element, &deleter);
}

{
	auto arr = SharedPtr!(long[]).make(10); //dynamic array with length 10
	assert(arr.length == 10);
}

Example

//alloc SharedPtr object
import std.experimental.allocator : make, dispose, allocatorObject;

auto allocator = allocatorObject(Mallocator.instance);

{
	auto x = SharedPtr!long.alloc(allocator, 42);
}

{
	static void deleter(long* x)pure nothrow @trusted @nogc{
		Mallocator.instance.dispose(x);
	}
	long* element = Mallocator.instance.make!long;
	auto x = SharedPtr!long.alloc(allocator, element, &deleter);
}

{
	auto arr = SharedPtr!(long[]).alloc(allocator, 10); //dynamic array with length 10
	assert(arr.length == 10);
}