Add memory.types.Scoped

This commit is contained in:
Eugen Wissner 2017-05-13 08:27:51 +02:00
parent 7a0241b484
commit ba2d086fb8

View File

@ -18,7 +18,7 @@ import std.range;
import std.traits; import std.traits;
import tanya.memory; import tanya.memory;
package(tanya) final class RefCountedStore(T) final class RefCountedStore(T)
{ {
T payload; T payload;
size_t counter = 1; size_t counter = 1;
@ -34,7 +34,7 @@ package(tanya) final class RefCountedStore(T)
mixin("return " ~ op ~ "counter;"); mixin("return " ~ op ~ "counter;");
} }
int opCmp(size_t counter) int opCmp(const size_t counter)
{ {
if (this.counter > counter) if (this.counter > counter)
{ {
@ -50,7 +50,7 @@ package(tanya) final class RefCountedStore(T)
} }
} }
int opEquals(size_t counter) int opEquals(const size_t counter)
{ {
return this.counter == counter; return this.counter == counter;
} }
@ -118,7 +118,12 @@ struct RefCounted(T)
this(allocator); this(allocator);
this.storage = allocator.make!Storage(); this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!Payload; this.deleter = &separateDeleter!Payload;
move(value, this.storage.payload); move(value, this.storage.payload);
static if (__traits(isRef, value))
{
value = null;
}
} }
/// Ditto. /// Ditto.
@ -139,7 +144,7 @@ struct RefCounted(T)
{ {
if (count != 0) if (count != 0)
{ {
++storage; ++this.storage;
} }
} }
@ -152,7 +157,7 @@ struct RefCounted(T)
{ {
if (this.storage !is null && !(this.storage.counter && --this.storage)) if (this.storage !is null && !(this.storage.counter && --this.storage))
{ {
deleter(storage, allocator); deleter(this.storage, allocator);
} }
} }
@ -163,14 +168,16 @@ struct RefCounted(T)
* If it is the last reference of the previously owned object, * If it is the last reference of the previously owned object,
* it will be destroyed. * it will be destroyed.
* *
* To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null). * To reset $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
* *
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will * If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
* be used. If you need a different allocator, create a new * be used. If you need a different allocator, create a new
* $(D_PSYMBOL RefCounted) and assign it. * $(D_PSYMBOL RefCounted) and assign it.
* *
* Params: * Params:
* rhs = $(D_KEYWORD this). * rhs = New object.
*
* Returns: $(D_KEYWORD this).
*/ */
ref typeof(this) opAssign()(auto ref Payload rhs) ref typeof(this) opAssign()(auto ref Payload rhs)
{ {
@ -187,8 +194,6 @@ struct RefCounted(T)
} }
else else
{ {
// Created with refCounted. Always destroyed togethter with the pointer.
assert(this.storage.counter != 0);
finalize(this.storage.payload); finalize(this.storage.payload);
this.storage.payload = Payload.init; this.storage.payload = Payload.init;
} }
@ -206,15 +211,13 @@ struct RefCounted(T)
else if (this.storage > 1) else if (this.storage > 1)
{ {
--this.storage; --this.storage;
this.storage = null;
} }
else else
{ {
// Created with refCounted. Always destroyed togethter with the pointer. deleter(this.storage, allocator);
assert(this.storage.counter != 0);
finalize(this.storage.payload);
this.storage.payload = Payload.init;
} }
this.storage = null;
return this; return this;
} }
@ -229,6 +232,8 @@ struct RefCounted(T)
/** /**
* Returns: Reference to the owned object. * Returns: Reference to the owned object.
*
* Precondition: $(D_INLINECODE cound > 0).
*/ */
inout(Payload) get() inout pure nothrow @safe @nogc inout(Payload) get() inout pure nothrow @safe @nogc
in in
@ -240,7 +245,7 @@ struct RefCounted(T)
return storage.payload; return storage.payload;
} }
static if (isPointer!Payload) version (D_Ddoc)
{ {
/** /**
* Params: * Params:
@ -251,6 +256,11 @@ struct RefCounted(T)
* *
* Returns: Reference to the pointed value. * Returns: Reference to the pointed value.
*/ */
ref T opUnary(string op)()
if (op == "*");
}
else static if (isPointer!Payload)
{
ref T opUnary(string op)() ref T opUnary(string op)()
if (op == "*") if (op == "*")
{ {
@ -352,6 +362,44 @@ private unittest
assert(rc.count == 1); assert(rc.count == 1);
} }
private unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int rc)
{
assert(rc.count == 2);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
assert(rc.count == 1);
func(rc);
assert(rc.count == 1);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
private unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(*rc == 5);
void func(RefCounted!int rc)
{
assert(rc.count == 2);
rc = defaultAllocator.refCounted!int(4);
assert(*rc == 4);
assert(rc.count == 1);
}
func(rc);
assert(*rc == 5);
}
private unittest private unittest
{ {
static assert(is(typeof(RefCounted!int.storage.payload) == int*)); static assert(is(typeof(RefCounted!int.storage.payload) == int*));
@ -420,7 +468,7 @@ body
/** /**
* Constructs a new array with $(D_PARAM size) elements and wraps it in a * Constructs a new array with $(D_PARAM size) elements and wraps it in a
* $(D_PSYMBOL RefCounted) using. * $(D_PSYMBOL RefCounted).
* *
* Params: * Params:
* T = Array type. * T = Array type.
@ -432,7 +480,8 @@ body
* Precondition: $(D_INLINECODE allocator !is null * Precondition: $(D_INLINECODE allocator !is null
* && size <= size_t.max / ElementType!T.sizeof) * && size <= size_t.max / ElementType!T.sizeof)
*/ */
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size) RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
@trusted
if (isArray!T) if (isArray!T)
in in
{ {
@ -441,8 +490,7 @@ in
} }
body body
{ {
auto payload = cast(T) allocator.allocate(ElementType!T.sizeof * size); auto payload = allocator.resize!(ElementType!T)(null, size);
initializeAll(payload);
return RefCounted!T(payload, allocator); return RefCounted!T(payload, allocator);
} }
@ -512,3 +560,257 @@ private @nogc unittest
} }
assert(destroyed); assert(destroyed);
} }
/**
* $(D_PSYMBOL Scoped) stores an object that gets destroyed at the end of its scope.
*
* Params:
* T = Value type.
*/
struct Scoped(T)
{
static if (is(T == class) || is(T == interface) || isArray!T)
{
private alias Payload = T;
}
else
{
private alias Payload = T*;
}
private Payload 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()(auto ref Payload value,
shared Allocator allocator = defaultAllocator)
{
this(allocator);
move(value, this.payload);
static if (__traits(isRef, value))
{
value = null;
}
}
/// Ditto.
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.allocator_ = allocator;
}
/**
* $(D_PSYMBOL Scoped) is noncopyable.
*/
@disable this(this);
/**
* Destroys the owned object.
*/
~this()
{
if (this.payload !is null)
{
allocator.dispose(this.payload);
}
}
/**
* Initialized this $(D_PARAM Scoped) and takes ownership over
* $(D_PARAM rhs).
*
* To reset $(D_PSYMBOL Scoped) 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 Scoped) and assign it.
*
* Params:
* rhs = New object.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign()(auto ref Payload rhs)
{
allocator.dispose(this.payload);
move(rhs, this.payload);
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;
}
/**
* Returns: Reference to the owned object.
*/
inout(Payload) get() inout pure nothrow @safe @nogc
{
return payload;
}
version (D_Ddoc)
{
/**
* Params:
* op = Operation.
*
* Dereferences the pointer. It is defined only for pointers, not for
* reference types like classes, that can be accessed directly.
*
* Returns: Reference to the pointed value.
*/
ref T opUnary(string op)()
if (op == "*");
}
else static if (isPointer!Payload)
{
ref T opUnary(string op)()
if (op == "*")
{
return *payload;
}
}
mixin DefaultAllocator;
alias get this;
}
///
@nogc unittest
{
auto p = defaultAllocator.make!int(5);
auto s = Scoped!int(p, defaultAllocator);
assert(p is null);
assert(*s == 5);
}
///
@nogc unittest
{
static bool destroyed = false;
struct F
{
~this() @nogc
{
destroyed = true;
}
}
{
auto s = Scoped!F(defaultAllocator.make!F(), defaultAllocator);
}
assert(destroyed);
}
/**
* Constructs a new object of type $(D_PARAM T) and wraps it in a
* $(D_PSYMBOL Scoped) 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 Scoped!T).
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
Scoped!T scoped(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T
&& !isAssociativeArray!T && !isArray!T)
in
{
assert(allocator !is null);
}
body
{
auto payload = allocator.make!(T, shared Allocator, A)(args);
return Scoped!T(payload, allocator);
}
/**
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
* $(D_PSYMBOL Scoped).
*
* Params:
* T = Array type.
* size = Array size.
* allocator = Allocator.
*
* Returns: Newly created $(D_PSYMBOL Scoped!T).
*
* Precondition: $(D_INLINECODE allocator !is null
* && size <= size_t.max / ElementType!T.sizeof)
*/
Scoped!T scoped(T)(shared Allocator allocator, const size_t size)
@trusted
if (isArray!T)
in
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
}
body
{
auto payload = allocator.resize!(ElementType!T)(null, size);
return Scoped!T(payload, allocator);
}
private unittest
{
static assert(is(typeof(defaultAllocator.scoped!B(5))));
static assert(is(typeof(defaultAllocator.scoped!(int[])(5))));
}
private unittest
{
auto s = defaultAllocator.scoped!int(5);
assert(*s == 5);
s = null;
assert(s is null);
}
private unittest
{
auto s = defaultAllocator.scoped!int(5);
assert(*s == 5);
s = defaultAllocator.scoped!int(4);
assert(*s == 4);
}