541 lines
13 KiB
D
541 lines
13 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/. */
|
|
|
|
/**
|
|
* Lifetime management functions, types and related exceptions.
|
|
*
|
|
* Copyright: Eugene Wissner 2019-2025.
|
|
* 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/lifetime.d,
|
|
* tanya/memory/lifetime.d)
|
|
*/
|
|
module tanya.memory.lifetime;
|
|
|
|
import tanya.memory.allocator;
|
|
import tanya.meta.metafunction;
|
|
import tanya.meta.trait;
|
|
|
|
package(tanya) void destroyAllImpl(R, E)(R p)
|
|
{
|
|
static if (hasElaborateDestructor!E)
|
|
{
|
|
foreach (ref e; p)
|
|
{
|
|
destroy(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs a new object of type $(D_PARAM T) in $(D_PARAM memory) with the
|
|
* given arguments.
|
|
*
|
|
* If $(D_PARAM T) is a $(D_KEYWORD class), emplace returns a class reference
|
|
* of type $(D_PARAM T), otherwise a pointer to the constructed object is
|
|
* returned.
|
|
*
|
|
* If $(D_PARAM T) is a nested class inside another class, $(D_PARAM outer)
|
|
* should be an instance of the outer class.
|
|
*
|
|
* $(D_PARAM args) are arguments for the constructor of $(D_PARAM T). If
|
|
* $(D_PARAM T) isn't an aggregate type and doesn't have a constructor,
|
|
* $(D_PARAM memory) can be initialized to `args[0]` if `Args.length == 1`,
|
|
* `Args[0]` should be implicitly convertible to $(D_PARAM T) then.
|
|
*
|
|
* Params:
|
|
* T = Constructed type.
|
|
* U = Type of the outer class if $(D_PARAM T) is a nested class.
|
|
* Args = Types of the constructor arguments if $(D_PARAM T) has a constructor
|
|
* or the type of the initial value.
|
|
* outer = Outer class instance if $(D_PARAM T) is a nested class.
|
|
* args = Constructor arguments if $(D_PARAM T) has a constructor or the
|
|
* initial value.
|
|
*
|
|
* Returns: New instance of type $(D_PARAM T) constructed in $(D_PARAM memory).
|
|
*
|
|
* Precondition: `memory.length == stateSize!T`.
|
|
* Postcondition: $(D_PARAM memory) and the result point to the same memory.
|
|
*/
|
|
T emplace(T, U, Args...)(void[] memory, U outer, auto ref Args args)
|
|
if (!isAbstractClass!T && isInnerClass!T && is(typeof(T.outer) == U))
|
|
in
|
|
{
|
|
assert(memory.length >= stateSize!T);
|
|
}
|
|
out (result)
|
|
{
|
|
assert(memory.ptr is (() @trusted => cast(void*) result)());
|
|
}
|
|
do
|
|
{
|
|
import tanya.memory.op : copy;
|
|
|
|
copy(typeid(T).initializer, memory);
|
|
|
|
auto result = (() @trusted => cast(T) memory.ptr)();
|
|
result.outer = outer;
|
|
|
|
static if (is(typeof(result.__ctor(args))))
|
|
{
|
|
result.__ctor(args);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// ditto
|
|
T emplace(T, Args...)(void[] memory, auto ref Args args)
|
|
if (is(T == class) && !isAbstractClass!T && !isInnerClass!T)
|
|
in
|
|
{
|
|
assert(memory.length == stateSize!T);
|
|
}
|
|
out (result)
|
|
{
|
|
assert(memory.ptr is (() @trusted => cast(void*) result)());
|
|
}
|
|
do
|
|
{
|
|
import tanya.memory.op : copy;
|
|
|
|
copy(typeid(T).initializer, memory);
|
|
|
|
auto result = (() @trusted => cast(T) memory.ptr)();
|
|
static if (is(typeof(result.__ctor(args))))
|
|
{
|
|
result.__ctor(args);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
class C
|
|
{
|
|
int i = 5;
|
|
class Inner
|
|
{
|
|
int i;
|
|
|
|
this(int param) pure nothrow @safe @nogc
|
|
{
|
|
this.i = param;
|
|
}
|
|
}
|
|
}
|
|
ubyte[stateSize!C] memory1;
|
|
ubyte[stateSize!(C.Inner)] memory2;
|
|
|
|
auto c = emplace!C(memory1);
|
|
assert(c.i == 5);
|
|
|
|
auto inner = emplace!(C.Inner)(memory2, c, 8);
|
|
assert(c.i == 5);
|
|
assert(inner.i == 8);
|
|
assert(inner.outer is c);
|
|
}
|
|
|
|
/// ditto
|
|
T* emplace(T, Args...)(void[] memory, auto ref Args args)
|
|
if (!isAggregateType!T && (Args.length <= 1))
|
|
in
|
|
{
|
|
assert(memory.length >= T.sizeof);
|
|
}
|
|
out (result)
|
|
{
|
|
assert(memory.ptr is result);
|
|
}
|
|
do
|
|
{
|
|
auto result = (() @trusted => cast(T*) memory.ptr)();
|
|
static if (Args.length == 1)
|
|
{
|
|
*result = T(args[0]);
|
|
}
|
|
else
|
|
{
|
|
*result = T.init;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private void initializeOne(T)(ref void[] memory, ref T* result) @trusted
|
|
{
|
|
import tanya.memory.op : copy, fill;
|
|
|
|
static if (!hasElaborateAssign!T && isAssignable!T)
|
|
{
|
|
*result = T.init;
|
|
}
|
|
else static if (__VERSION__ >= 2083 // __traits(isZeroInit) available.
|
|
&& __traits(isZeroInit, T))
|
|
{
|
|
memory.ptr[0 .. T.sizeof].fill!0;
|
|
}
|
|
else
|
|
{
|
|
static immutable T init = T.init;
|
|
copy((&init)[0 .. 1], memory);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
T* emplace(T, Args...)(void[] memory, auto ref Args args)
|
|
if (!isPolymorphicType!T && isAggregateType!T)
|
|
in
|
|
{
|
|
assert(memory.length >= T.sizeof);
|
|
}
|
|
out (result)
|
|
{
|
|
assert(memory.ptr is result);
|
|
}
|
|
do
|
|
{
|
|
auto result = (() @trusted => cast(T*) memory.ptr)();
|
|
|
|
static if (Args.length == 0)
|
|
{
|
|
static assert(is(typeof({ static T t; })),
|
|
"Default constructor is disabled");
|
|
initializeOne(memory, result);
|
|
}
|
|
else static if (is(typeof(result.__ctor(args))))
|
|
{
|
|
initializeOne(memory, result);
|
|
result.__ctor(args);
|
|
}
|
|
else static if (Args.length == 1 && is(typeof({ T t = args[0]; })))
|
|
{
|
|
import tanya.memory.op : copy;
|
|
|
|
((ref arg) @trusted =>
|
|
copy((cast(void*) &arg)[0 .. T.sizeof], memory))(args[0]);
|
|
static if (hasElaborateCopyConstructor!T)
|
|
{
|
|
result.__postblit();
|
|
}
|
|
}
|
|
else static if (is(typeof({ T t = T(args); })))
|
|
{
|
|
auto init = T(args);
|
|
(() @trusted => moveEmplace(init, *result))();
|
|
}
|
|
else
|
|
{
|
|
static assert(false,
|
|
"Unable to construct value with the given arguments");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
ubyte[4] memory;
|
|
|
|
auto i = emplace!int(memory);
|
|
static assert(is(typeof(i) == int*));
|
|
assert(*i == 0);
|
|
|
|
i = emplace!int(memory, 5);
|
|
assert(*i == 5);
|
|
|
|
static struct S
|
|
{
|
|
int i;
|
|
@disable this();
|
|
@disable this(this);
|
|
this(int i) @nogc nothrow pure @safe
|
|
{
|
|
this.i = i;
|
|
}
|
|
}
|
|
auto s = emplace!S(memory, 8);
|
|
static assert(is(typeof(s) == S*));
|
|
assert(s.i == 8);
|
|
}
|
|
|
|
private void deinitialize(bool zero, T)(ref T value)
|
|
{
|
|
static if (is(T == U[S], U, size_t S))
|
|
{
|
|
foreach (ref e; value)
|
|
{
|
|
deinitialize!zero(e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import tanya.memory.op : copy, fill;
|
|
|
|
static if (isNested!T)
|
|
{
|
|
// Don't override the context pointer.
|
|
enum size_t size = T.sizeof - (void*).sizeof;
|
|
}
|
|
else
|
|
{
|
|
enum size_t size = T.sizeof;
|
|
}
|
|
static if (zero)
|
|
{
|
|
fill!0((cast(void*) &value)[0 .. size]);
|
|
}
|
|
else
|
|
{
|
|
copy(typeid(T).initializer()[0 .. size], (&value)[0 .. 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Moves $(D_PARAM source) into $(D_PARAM target) assuming that
|
|
* $(D_PARAM target) isn't initialized.
|
|
*
|
|
* Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places
|
|
* the $(D_PARAM source) into a valid but unspecified state, which means that
|
|
* after moving $(D_PARAM source) can be destroyed or assigned a new value, but
|
|
* accessing it yields an unspecified value. No postblits or destructors are
|
|
* called. If the $(D_PARAM target) should be destroyed before, use
|
|
* $(D_PSYMBOL move).
|
|
*
|
|
* $(D_PARAM source) and $(D_PARAM target) must be different objects.
|
|
*
|
|
* Params:
|
|
* T = Object type.
|
|
* source = Source object.
|
|
* target = Target object.
|
|
*
|
|
* See_Also: $(D_PSYMBOL move),
|
|
* $(D_PSYMBOL hasElaborateCopyConstructor),
|
|
* $(D_PSYMBOL hasElaborateDestructor).
|
|
*
|
|
* Precondition: `&source !is &target`.
|
|
*/
|
|
void moveEmplace(T)(ref T source, ref T target) @system
|
|
in
|
|
{
|
|
assert(&source !is &target, "Source and target must be different");
|
|
}
|
|
do
|
|
{
|
|
static if (is(T == struct) || isStaticArray!T)
|
|
{
|
|
import tanya.memory.op : copy;
|
|
|
|
copy((&source)[0 .. 1], (&target)[0 .. 1]);
|
|
|
|
static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T)
|
|
{
|
|
static if (__VERSION__ >= 2083) // __traits(isZeroInit) available.
|
|
{
|
|
deinitialize!(__traits(isZeroInit, T))(source);
|
|
}
|
|
else
|
|
{
|
|
if (typeid(T).initializer().ptr is null)
|
|
{
|
|
deinitialize!true(source);
|
|
}
|
|
else
|
|
{
|
|
deinitialize!false(source);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
target = source;
|
|
}
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @system unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int member = 5;
|
|
|
|
this(this) @nogc nothrow pure @safe
|
|
{
|
|
assert(false);
|
|
}
|
|
}
|
|
S source, target = void;
|
|
moveEmplace(source, target);
|
|
assert(target.member == 5);
|
|
|
|
int x1 = 5, x2;
|
|
moveEmplace(x1, x2);
|
|
assert(x2 == 5);
|
|
}
|
|
|
|
/**
|
|
* Moves $(D_PARAM source) into $(D_PARAM target) assuming that
|
|
* $(D_PARAM target) isn't initialized.
|
|
*
|
|
* Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places
|
|
* the $(D_PARAM source) into a valid but unspecified state, which means that
|
|
* after moving $(D_PARAM source) can be destroyed or assigned a new value, but
|
|
* accessing it yields an unspecified value. $(D_PARAM target) is destroyed before
|
|
* the new value is assigned. If $(D_PARAM target) isn't initialized and
|
|
* therefore shouldn't be destroyed, $(D_PSYMBOL moveEmplace) can be used.
|
|
*
|
|
* If $(D_PARAM target) isn't specified, $(D_PSYMBOL move) returns the source
|
|
* as rvalue without calling its copy constructor or destructor.
|
|
*
|
|
* $(D_PARAM source) and $(D_PARAM target) are the same object,
|
|
* $(D_PSYMBOL move) does nothing.
|
|
*
|
|
* Params:
|
|
* T = Object type.
|
|
* source = Source object.
|
|
* target = Target object.
|
|
*
|
|
* See_Also: $(D_PSYMBOL moveEmplace).
|
|
*/
|
|
void move(T)(ref T source, ref T target)
|
|
{
|
|
if ((() @trusted => &source is &target)())
|
|
{
|
|
return;
|
|
}
|
|
static if (hasElaborateDestructor!T)
|
|
{
|
|
target.__xdtor();
|
|
}
|
|
(() @trusted => moveEmplace(source, target))();
|
|
}
|
|
|
|
/// ditto
|
|
T move(T)(ref T source) @trusted
|
|
{
|
|
static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T)
|
|
{
|
|
T target = void;
|
|
moveEmplace(source, target);
|
|
return target;
|
|
}
|
|
else
|
|
{
|
|
return source;
|
|
}
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
static struct S
|
|
{
|
|
int member = 5;
|
|
|
|
this(this) @nogc nothrow pure @safe
|
|
{
|
|
assert(false);
|
|
}
|
|
}
|
|
S source, target = void;
|
|
move(source, target);
|
|
assert(target.member == 5);
|
|
assert(move(target).member == 5);
|
|
|
|
int x1 = 5, x2;
|
|
move(x1, x2);
|
|
assert(x2 == 5);
|
|
assert(move(x2) == 5);
|
|
}
|
|
|
|
/**
|
|
* Exchanges the values of $(D_PARAM a) and $(D_PARAM b).
|
|
*
|
|
* $(D_PSYMBOL swap) moves the contents of $(D_PARAM a) and $(D_PARAM b)
|
|
* without calling its postblits or destructors.
|
|
*
|
|
* Params:
|
|
* a = The first object.
|
|
* b = The second object.
|
|
*/
|
|
void swap(T)(ref T a, ref T b) @trusted
|
|
{
|
|
T tmp = void;
|
|
moveEmplace(a, tmp);
|
|
moveEmplace(b, a);
|
|
moveEmplace(tmp, b);
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
int a = 3, b = 5;
|
|
swap(a, b);
|
|
assert(a == 5);
|
|
assert(b == 3);
|
|
}
|
|
|
|
/**
|
|
* Forwards its argument list preserving $(D_KEYWORD ref) and $(D_KEYWORD out)
|
|
* storage classes.
|
|
*
|
|
* $(D_PSYMBOL forward) accepts a list of variables or literals. It returns an
|
|
* argument list of the same length that can be for example passed to a
|
|
* function accepting the arguments of this type.
|
|
*
|
|
* Params:
|
|
* args = Argument list.
|
|
*
|
|
* Returns: $(D_PARAM args) with their original storage classes.
|
|
*/
|
|
template forward(args...)
|
|
{
|
|
static if (args.length == 0)
|
|
{
|
|
alias forward = AliasSeq!();
|
|
}
|
|
else static if (__traits(isRef, args[0]) || __traits(isOut, args[0]))
|
|
{
|
|
static if (args.length == 1)
|
|
{
|
|
alias forward = args[0];
|
|
}
|
|
else
|
|
{
|
|
alias forward = AliasSeq!(args[0], forward!(args[1 .. $]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
@property auto forwardOne()
|
|
{
|
|
return move(args[0]);
|
|
}
|
|
static if (args.length == 1)
|
|
{
|
|
alias forward = forwardOne;
|
|
}
|
|
else
|
|
{
|
|
alias forward = AliasSeq!(forwardOne, forward!(args[1 .. $]));
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
static assert(is(typeof((int i) { int v = forward!i; })));
|
|
static assert(is(typeof((ref int i) { int v = forward!i; })));
|
|
static assert(is(typeof({
|
|
void f(int i, ref int j, out int k)
|
|
{
|
|
f(forward!(i, j, k));
|
|
}
|
|
})));
|
|
}
|