636 lines
15 KiB
D
636 lines
15 KiB
D
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* Smart pointers.
|
|
*
|
|
* A smart pointer is an object that wraps a raw pointer or a reference
|
|
* (class, dynamic array) to manage its lifetime.
|
|
*
|
|
* This module provides two kinds of lifetime management strategies:
|
|
* $(UL
|
|
* $(LI Reference counting)
|
|
* $(LI Unique ownership)
|
|
* )
|
|
*
|
|
* Copyright: Eugene Wissner 2016-2019.
|
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
* Mozilla Public License, v. 2.0).
|
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
|
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/smartref.d,
|
|
* tanya/memory/smartref.d)
|
|
*/
|
|
module tanya.memory.smartref;
|
|
|
|
import tanya.memory.allocator;
|
|
import tanya.memory.lifetime;
|
|
import tanya.meta.trait;
|
|
|
|
private template Payload(T)
|
|
{
|
|
static if (isPolymorphicType!T || isDynamicArray!T)
|
|
{
|
|
alias Payload = T;
|
|
}
|
|
else
|
|
{
|
|
alias Payload = T*;
|
|
}
|
|
}
|
|
|
|
private final class RefCountedStore(T)
|
|
{
|
|
T payload;
|
|
size_t counter = 1;
|
|
|
|
size_t opUnary(string op)()
|
|
if (op == "--" || op == "++")
|
|
in (this.counter > 0)
|
|
{
|
|
mixin("return " ~ op ~ "counter;");
|
|
}
|
|
|
|
int opCmp(const size_t counter)
|
|
{
|
|
if (this.counter > counter)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (this.counter < counter)
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void separateDeleter(T)(RefCountedStore!T storage,
|
|
shared Allocator allocator)
|
|
{
|
|
allocator.dispose(storage.payload);
|
|
allocator.dispose(storage);
|
|
}
|
|
|
|
private void unifiedDeleter(T)(RefCountedStore!T storage,
|
|
shared Allocator allocator)
|
|
{
|
|
auto ptr1 = finalize(storage);
|
|
auto ptr2 = finalize(storage.payload);
|
|
allocator.deallocate(ptr1.ptr[0 .. ptr1.length + ptr2.length]);
|
|
}
|
|
|
|
/**
|
|
* Reference-counted object containing a $(D_PARAM T) value as payload.
|
|
* $(D_PSYMBOL RefCounted) keeps track of all references of an object, and
|
|
* when the reference count goes down to zero, frees the underlying store.
|
|
*
|
|
* Params:
|
|
* T = Type of the reference-counted value.
|
|
*/
|
|
struct RefCounted(T)
|
|
{
|
|
private alias Storage = RefCountedStore!(Payload!T);
|
|
|
|
private Storage storage;
|
|
private void function(Storage storage,
|
|
shared Allocator allocator) @nogc deleter;
|
|
|
|
invariant
|
|
{
|
|
assert(this.storage is null || this.allocator_ !is null);
|
|
assert(this.storage is null || this.deleter !is null);
|
|
}
|
|
|
|
/**
|
|
* Takes ownership over $(D_PARAM value), setting the counter to 1.
|
|
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
|
|
*
|
|
* Params:
|
|
* value = Value whose ownership is taken over.
|
|
* allocator = Allocator used to destroy the $(D_PARAM value) and to
|
|
* allocate/deallocate internal storage.
|
|
*
|
|
* Precondition: $(D_INLINECODE allocator !is null)
|
|
*/
|
|
this(Payload!T value, shared Allocator allocator = defaultAllocator)
|
|
{
|
|
this(allocator);
|
|
this.storage = allocator.make!Storage();
|
|
this.deleter = &separateDeleter!(Payload!T);
|
|
|
|
this.storage.payload = value;
|
|
}
|
|
|
|
/// ditto
|
|
this(shared Allocator allocator)
|
|
in (allocator !is null)
|
|
{
|
|
this.allocator_ = allocator;
|
|
}
|
|
|
|
/**
|
|
* Increases the reference counter by one.
|
|
*/
|
|
this(this)
|
|
{
|
|
if (count != 0)
|
|
{
|
|
++this.storage;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decreases the reference counter by one.
|
|
*
|
|
* If the counter reaches 0, destroys the owned object.
|
|
*/
|
|
~this()
|
|
{
|
|
if (this.storage !is null && !(this.storage > 0 && --this.storage))
|
|
{
|
|
deleter(this.storage, allocator);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes ownership over $(D_PARAM rhs). Initializes this
|
|
* $(D_PSYMBOL RefCounted) if needed.
|
|
*
|
|
* If it is the last reference of the previously owned object,
|
|
* it will be destroyed.
|
|
*
|
|
* To reset $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
|
|
*
|
|
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
|
* be used. If you need a different allocator, create a new
|
|
* $(D_PSYMBOL RefCounted) and assign it.
|
|
*
|
|
* Params:
|
|
* rhs = New object.
|
|
*
|
|
* Returns: $(D_KEYWORD this).
|
|
*/
|
|
ref typeof(this) opAssign(Payload!T rhs)
|
|
{
|
|
if (this.storage is null)
|
|
{
|
|
this.storage = allocator.make!Storage();
|
|
this.deleter = &separateDeleter!(Payload!T);
|
|
}
|
|
else if (this.storage > 1)
|
|
{
|
|
--this.storage;
|
|
this.storage = allocator.make!Storage();
|
|
this.deleter = &separateDeleter!(Payload!T);
|
|
}
|
|
else
|
|
{
|
|
finalize(this.storage.payload);
|
|
this.storage.payload = Payload!T.init;
|
|
}
|
|
this.storage.payload = rhs;
|
|
return this;
|
|
}
|
|
|
|
/// ditto
|
|
ref typeof(this) opAssign(typeof(null))
|
|
{
|
|
if (this.storage is null)
|
|
{
|
|
return this;
|
|
}
|
|
else if (this.storage > 1)
|
|
{
|
|
--this.storage;
|
|
}
|
|
else
|
|
{
|
|
deleter(this.storage, allocator);
|
|
}
|
|
this.storage = null;
|
|
|
|
return this;
|
|
}
|
|
|
|
/// ditto
|
|
ref typeof(this) opAssign(typeof(this) rhs)
|
|
{
|
|
swap(this.allocator_, rhs.allocator_);
|
|
swap(this.storage, rhs.storage);
|
|
swap(this.deleter, rhs.deleter);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Returns: Reference to the owned object.
|
|
*
|
|
* Precondition: $(D_INLINECODE cound > 0).
|
|
*/
|
|
inout(Payload!T) get() inout
|
|
in (count > 0, "Attempted to access an uninitialized reference")
|
|
{
|
|
return this.storage.payload;
|
|
}
|
|
|
|
version (D_Ddoc)
|
|
{
|
|
/**
|
|
* Dereferences the pointer. It is defined only for pointers, not for
|
|
* reference types like classes, that can be accessed directly.
|
|
*
|
|
* Params:
|
|
* op = Operation.
|
|
*
|
|
* Returns: Reference to the pointed value.
|
|
*/
|
|
ref inout(T) opUnary(string op)() inout
|
|
if (op == "*");
|
|
}
|
|
else static if (isPointer!(Payload!T))
|
|
{
|
|
ref inout(T) opUnary(string op)() inout
|
|
if (op == "*")
|
|
{
|
|
return *this.storage.payload;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
|
|
* storage.
|
|
*/
|
|
@property bool isInitialized() const
|
|
{
|
|
return this.storage !is null;
|
|
}
|
|
|
|
/**
|
|
* Returns: The number of $(D_PSYMBOL RefCounted) instances that share
|
|
* ownership over the same pointer (including $(D_KEYWORD this)).
|
|
* If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`.
|
|
*/
|
|
@property size_t count() const
|
|
{
|
|
return this.storage is null ? 0 : this.storage.counter;
|
|
}
|
|
|
|
mixin DefaultAllocator;
|
|
alias get this;
|
|
}
|
|
|
|
///
|
|
@nogc @system unittest
|
|
{
|
|
auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
|
|
auto val = rc.get();
|
|
|
|
*val = 8;
|
|
assert(*rc.get == 8);
|
|
|
|
val = null;
|
|
assert(rc.get !is null);
|
|
assert(*rc.get == 8);
|
|
|
|
*rc = 9;
|
|
assert(*rc.get == 9);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new object of type $(D_PARAM T) and wraps it in a
|
|
* $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for
|
|
* the constructor of $(D_PARAM T).
|
|
*
|
|
* This function is more efficient than the using of $(D_PSYMBOL RefCounted)
|
|
* directly, since it allocates only ones (the internal storage and the
|
|
* object).
|
|
*
|
|
* Params:
|
|
* T = Type of the constructed object.
|
|
* A = Types of the arguments to the constructor of $(D_PARAM T).
|
|
* allocator = Allocator.
|
|
* args = Constructor arguments of $(D_PARAM T).
|
|
*
|
|
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
|
|
*
|
|
* Precondition: $(D_INLINECODE allocator !is null)
|
|
*/
|
|
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
|
|
if (!is(T == interface) && !isAbstractClass!T
|
|
&& !isAssociativeArray!T && !isArray!T)
|
|
in (allocator !is null)
|
|
{
|
|
auto rc = typeof(return)(allocator);
|
|
|
|
const storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
|
|
const size = alignedSize(stateSize!T + storageSize);
|
|
|
|
auto mem = (() @trusted => allocator.allocate(size))();
|
|
if (mem is null)
|
|
{
|
|
onOutOfMemoryError();
|
|
}
|
|
scope (failure)
|
|
{
|
|
() @trusted { allocator.deallocate(mem); }();
|
|
}
|
|
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
|
|
rc.storage.payload = emplace!T(mem[storageSize .. $], args);
|
|
|
|
rc.deleter = &unifiedDeleter!(Payload!T);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
|
* $(D_PSYMBOL RefCounted).
|
|
*
|
|
* Params:
|
|
* T = Array type.
|
|
* E = Array element type.
|
|
* size = Array size.
|
|
* allocator = Allocator.
|
|
*
|
|
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
|
|
*
|
|
* Precondition: $(D_INLINECODE allocator !is null
|
|
* && size <= size_t.max / E.sizeof)
|
|
*/
|
|
RefCounted!T refCounted(T : E[], E)(shared Allocator allocator, size_t size)
|
|
@trusted
|
|
in (allocator !is null)
|
|
in (size <= size_t.max / E.sizeof)
|
|
{
|
|
return RefCounted!T(allocator.make!T(size), allocator);
|
|
}
|
|
|
|
///
|
|
@nogc @system unittest
|
|
{
|
|
auto rc = defaultAllocator.refCounted!int(5);
|
|
assert(rc.count == 1);
|
|
|
|
void func(RefCounted!int param) @nogc
|
|
{
|
|
if (param.count == 2)
|
|
{
|
|
func(param);
|
|
}
|
|
else
|
|
{
|
|
assert(param.count == 3);
|
|
}
|
|
}
|
|
func(rc);
|
|
|
|
assert(rc.count == 1);
|
|
}
|
|
|
|
/**
|
|
* $(D_PSYMBOL Unique) stores an object that gets destroyed at the end of its scope.
|
|
*
|
|
* Params:
|
|
* T = Value type.
|
|
*/
|
|
struct Unique(T)
|
|
{
|
|
private Payload!T payload;
|
|
|
|
invariant
|
|
{
|
|
assert(payload is null || allocator_ !is null);
|
|
}
|
|
|
|
/**
|
|
* Takes ownership over $(D_PARAM value), setting the counter to 1.
|
|
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
|
|
*
|
|
* Params:
|
|
* value = Value whose ownership is taken over.
|
|
* allocator = Allocator used to destroy the $(D_PARAM value) and to
|
|
* allocate/deallocate internal storage.
|
|
*
|
|
* Precondition: $(D_INLINECODE allocator !is null)
|
|
*/
|
|
this(Payload!T value, shared Allocator allocator = defaultAllocator)
|
|
{
|
|
this(allocator);
|
|
this.payload = value;
|
|
}
|
|
|
|
/// ditto
|
|
this(shared Allocator allocator)
|
|
in (allocator !is null)
|
|
{
|
|
this.allocator_ = allocator;
|
|
}
|
|
|
|
/**
|
|
* $(D_PSYMBOL Unique) is noncopyable.
|
|
*/
|
|
@disable this(this);
|
|
|
|
/**
|
|
* Destroys the owned object.
|
|
*/
|
|
~this()
|
|
{
|
|
allocator.dispose(this.payload);
|
|
}
|
|
|
|
/**
|
|
* Initialized this $(D_PARAM Unique) and takes ownership over
|
|
* $(D_PARAM rhs).
|
|
*
|
|
* To reset $(D_PSYMBOL Unique) assign $(D_KEYWORD null).
|
|
*
|
|
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
|
* be used. If you need a different allocator, create a new
|
|
* $(D_PSYMBOL Unique) and assign it.
|
|
*
|
|
* Params:
|
|
* rhs = New object.
|
|
*
|
|
* Returns: $(D_KEYWORD this).
|
|
*/
|
|
ref typeof(this) opAssign(Payload!T rhs)
|
|
{
|
|
allocator.dispose(this.payload);
|
|
this.payload = rhs;
|
|
return this;
|
|
}
|
|
|
|
/// ditto
|
|
ref typeof(this) opAssign(typeof(null))
|
|
{
|
|
allocator.dispose(this.payload);
|
|
return this;
|
|
}
|
|
|
|
/// ditto
|
|
ref typeof(this) opAssign(typeof(this) rhs)
|
|
{
|
|
swap(this.allocator_, rhs.allocator_);
|
|
swap(this.payload, rhs.payload);
|
|
|
|
return this;
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @system unittest
|
|
{
|
|
auto rc = defaultAllocator.unique!int(5);
|
|
rc = defaultAllocator.make!int(7);
|
|
assert(*rc == 7);
|
|
}
|
|
|
|
/**
|
|
* Returns: Reference to the owned object.
|
|
*/
|
|
inout(Payload!T) get() inout
|
|
{
|
|
return this.payload;
|
|
}
|
|
|
|
version (D_Ddoc)
|
|
{
|
|
/**
|
|
* Dereferences the pointer. It is defined only for pointers, not for
|
|
* reference types like classes, that can be accessed directly.
|
|
*
|
|
* Params:
|
|
* op = Operation.
|
|
*
|
|
* Returns: Reference to the pointed value.
|
|
*/
|
|
ref inout(T) opUnary(string op)() inout
|
|
if (op == "*");
|
|
}
|
|
else static if (isPointer!(Payload!T))
|
|
{
|
|
ref inout(T) opUnary(string op)() inout
|
|
if (op == "*")
|
|
{
|
|
return *this.payload;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns: Whether this $(D_PSYMBOL Unique) holds some value.
|
|
*/
|
|
@property bool isInitialized() const
|
|
{
|
|
return this.payload !is null;
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @system unittest
|
|
{
|
|
Unique!int u;
|
|
assert(!u.isInitialized);
|
|
}
|
|
|
|
/**
|
|
* Sets the internal pointer to $(D_KEYWORD). The allocator isn't changed.
|
|
*
|
|
* Returns: Reference to the owned object.
|
|
*/
|
|
Payload!T release()
|
|
{
|
|
auto payload = this.payload;
|
|
this.payload = null;
|
|
return payload;
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @system unittest
|
|
{
|
|
auto u = defaultAllocator.unique!int(5);
|
|
assert(u.isInitialized);
|
|
|
|
auto i = u.release();
|
|
assert(*i == 5);
|
|
assert(!u.isInitialized);
|
|
}
|
|
|
|
mixin DefaultAllocator;
|
|
alias get this;
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @system unittest
|
|
{
|
|
auto p = defaultAllocator.make!int(5);
|
|
auto s = Unique!int(p, defaultAllocator);
|
|
assert(*s == 5);
|
|
}
|
|
|
|
///
|
|
@nogc nothrow @system unittest
|
|
{
|
|
static bool destroyed;
|
|
|
|
static struct F
|
|
{
|
|
~this() @nogc nothrow @safe
|
|
{
|
|
destroyed = true;
|
|
}
|
|
}
|
|
{
|
|
auto s = Unique!F(defaultAllocator.make!F(), defaultAllocator);
|
|
}
|
|
assert(destroyed);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new object of type $(D_PARAM T) and wraps it in a
|
|
* $(D_PSYMBOL Unique) using $(D_PARAM args) as the parameter list for
|
|
* the constructor of $(D_PARAM T).
|
|
*
|
|
* Params:
|
|
* T = Type of the constructed object.
|
|
* A = Types of the arguments to the constructor of $(D_PARAM T).
|
|
* allocator = Allocator.
|
|
* args = Constructor arguments of $(D_PARAM T).
|
|
*
|
|
* Returns: Newly created $(D_PSYMBOL Unique!T).
|
|
*
|
|
* Precondition: $(D_INLINECODE allocator !is null)
|
|
*/
|
|
Unique!T unique(T, A...)(shared Allocator allocator, auto ref A args)
|
|
if (!is(T == interface) && !isAbstractClass!T
|
|
&& !isAssociativeArray!T && !isArray!T)
|
|
in (allocator !is null)
|
|
{
|
|
auto payload = allocator.make!(T, A)(args);
|
|
return Unique!T(payload, allocator);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
|
* $(D_PSYMBOL Unique).
|
|
*
|
|
* Params:
|
|
* T = Array type.
|
|
* E = Array element type.
|
|
* size = Array size.
|
|
* allocator = Allocator.
|
|
*
|
|
* Returns: Newly created $(D_PSYMBOL Unique!T).
|
|
*
|
|
* Precondition: $(D_INLINECODE allocator !is null
|
|
* && size <= size_t.max / E.sizeof)
|
|
*/
|
|
Unique!T unique(T : E[], E)(shared Allocator allocator, size_t size)
|
|
@trusted
|
|
in (allocator !is null)
|
|
in (size <= size_t.max / E.sizeof)
|
|
{
|
|
auto payload = allocator.resize!E(null, size);
|
|
return Unique!T(payload, allocator);
|
|
}
|