mirror of
https://github.com/dlang/phobos.git
synced 2025-04-28 22:21:09 +03:00
Merge pull request #3139 from mrkline/better-unique
Improve std.typecons.Unique
This commit is contained in:
commit
8f4a85bc83
1 changed files with 224 additions and 116 deletions
340
std/typecons.d
340
std/typecons.d
|
@ -50,11 +50,16 @@ import std.typetuple; // : TypeTuple, allSatisfy;
|
|||
debug(Unique) import std.stdio;
|
||||
|
||||
/**
|
||||
Encapsulates unique ownership of a resource. Resource of type $(D T) is
|
||||
deleted at the end of the scope, unless it is transferred. The
|
||||
transfer can be explicit, by calling $(D release), or implicit, when
|
||||
returning Unique from a function. The resource can be a polymorphic
|
||||
class object, in which case Unique behaves polymorphically too.
|
||||
Encapsulates unique ownership of a resource.
|
||||
|
||||
Like C++'s $(LINK2 http://en.cppreference.com/w/cpp/memory/unique_ptr, std::unique_ptr),
|
||||
a $(D Unique) maintains sole ownership of a given resource of type $(D T) until
|
||||
ownership is transferred or the $(D Unique) falls out of scope.
|
||||
Such a transfer can be explicit, using
|
||||
$(LINK2 http://dlang.org/phobos/std_algorithm_mutation.html#.move, $(D std.algorithm.move)),
|
||||
or implicit, when returning Unique from a function that created it.
|
||||
The resource can be a polymorphic class object,
|
||||
in which case Unique behaves polymorphically too.
|
||||
*/
|
||||
struct Unique(T)
|
||||
{
|
||||
|
@ -64,67 +69,18 @@ static if (is(T:Object))
|
|||
else
|
||||
alias RefT = T*;
|
||||
|
||||
public:
|
||||
// Deferred in case we get some language support for checking uniqueness.
|
||||
version(None)
|
||||
/**
|
||||
Allows safe construction of $(D Unique). It creates the resource and
|
||||
guarantees unique ownership of it (unless $(D T) publishes aliases of
|
||||
$(D this)).
|
||||
Note: Nested structs/classes cannot be created.
|
||||
Params:
|
||||
args = Arguments to pass to $(D T)'s constructor.
|
||||
---
|
||||
static class C {}
|
||||
auto u = Unique!(C).create();
|
||||
---
|
||||
*/
|
||||
static Unique!T create(A...)(auto ref A args)
|
||||
if (__traits(compiles, new T(args)))
|
||||
{
|
||||
debug(Unique) writeln("Unique.create for ", T.stringof);
|
||||
Unique!T u;
|
||||
u._p = new T(args);
|
||||
return u;
|
||||
}
|
||||
|
||||
/**
|
||||
Constructor that takes an rvalue.
|
||||
It will ensure uniqueness, as long as the rvalue
|
||||
isn't just a view on an lvalue (e.g., a cast).
|
||||
Typical usage:
|
||||
----
|
||||
Unique!Foo f = new Foo;
|
||||
----
|
||||
*/
|
||||
this(RefT p)
|
||||
{
|
||||
debug(Unique) writeln("Unique constructor with rvalue");
|
||||
_p = p;
|
||||
}
|
||||
/**
|
||||
Constructor that takes an lvalue. It nulls its source.
|
||||
The nulling will ensure uniqueness as long as there
|
||||
are no previous aliases to the source.
|
||||
*/
|
||||
this(ref RefT p)
|
||||
{
|
||||
_p = p;
|
||||
debug(Unique) writeln("Unique constructor nulling source");
|
||||
p = null;
|
||||
assert(p is null);
|
||||
}
|
||||
/**
|
||||
Constructor that takes a $(D Unique) of a type that is convertible to our type.
|
||||
|
||||
Typically used to transfer a $(D Unique) rvalue of derived type to
|
||||
a $(D Unique) of base type.
|
||||
|
||||
Example:
|
||||
---
|
||||
class C : Object {}
|
||||
class C : Object { }
|
||||
|
||||
Unique!C uc = new C;
|
||||
Unique!Object uo = uc.release;
|
||||
Unique!C uc = unique!C();
|
||||
Unique!Object uo = move(uc);
|
||||
---
|
||||
*/
|
||||
this(U)(Unique!U u)
|
||||
|
@ -146,39 +102,185 @@ public:
|
|||
u._p = null;
|
||||
}
|
||||
|
||||
/// Destroying a $(D Unique) frees the underlying resource.
|
||||
~this()
|
||||
{
|
||||
import core.stdc.stdlib : free;
|
||||
|
||||
debug(Unique) writeln("Unique destructor of ", (_p is null)? null: _p);
|
||||
if (_p !is null) delete _p;
|
||||
_p = null;
|
||||
if (_p !is null)
|
||||
{
|
||||
destroy(_p);
|
||||
|
||||
static if (hasIndirections!T)
|
||||
{
|
||||
import core.memory : GC;
|
||||
GC.removeRange(cast(void*)_p);
|
||||
}
|
||||
|
||||
free(cast(void*)_p);
|
||||
_p = null;
|
||||
}
|
||||
}
|
||||
/** Returns whether the resource exists. */
|
||||
@property bool isEmpty() const
|
||||
{
|
||||
return _p is null;
|
||||
}
|
||||
/** Transfer ownership to a $(D Unique) rvalue. Nullifies the current contents. */
|
||||
|
||||
/// Transfer ownership to a $(D Unique) rvalue.
|
||||
deprecated("Please use std.algorithm.move to transfer ownership.")
|
||||
Unique release()
|
||||
{
|
||||
import std.algorithm : move;
|
||||
|
||||
debug(Unique) writeln("Release");
|
||||
auto u = Unique(_p);
|
||||
Unique u = move(this);
|
||||
assert(_p is null);
|
||||
debug(Unique) writeln("return from Release");
|
||||
return u;
|
||||
}
|
||||
/** Forwards member access to contents. */
|
||||
RefT opDot() { return _p; }
|
||||
|
||||
/**
|
||||
Postblit operator is undefined to prevent the cloning of $(D Unique) objects.
|
||||
Returns a reference to the underlying $(D RefT) for use by non-owning code.
|
||||
|
||||
The holder of a $(D Unique!T) is the $(I owner) of that $(D T).
|
||||
For code that does not own the resource (and therefore does not affect
|
||||
its life cycle), pass a plain old reference.
|
||||
*/
|
||||
ref T get()() return @safe
|
||||
if (!is(T == class))
|
||||
{
|
||||
import std.exception : enforce;
|
||||
enforce(!empty, "You cannot get a struct reference from an empty Unique");
|
||||
return *_p;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a the underlying $(D T) for use by non-owning code.
|
||||
|
||||
Note that getting a class reference is currently unsafe
|
||||
as there is currently no way to stop it from escaping. (see DIP69)
|
||||
*/
|
||||
T get()() @system
|
||||
if (is(T == class))
|
||||
{
|
||||
return _p;
|
||||
}
|
||||
|
||||
/// Returns true if the $(D Unique) currently owns an underlying $(D T)
|
||||
@property bool empty() const
|
||||
{
|
||||
return _p is null;
|
||||
}
|
||||
|
||||
/// Allows the $(D Unique) to cast to a boolean value matching
|
||||
/// that of $(D Unique.empty)
|
||||
bool opCast(T : bool)() const { return !empty; }
|
||||
|
||||
/// Forwards the underlying $(D RefT)
|
||||
alias get this;
|
||||
|
||||
/// Postblit operator is undefined to prevent the cloning of $(D Unique) objects.
|
||||
@disable this(this);
|
||||
|
||||
private:
|
||||
RefT _p;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
// Ditto...
|
||||
import std.algorithm;
|
||||
|
||||
static class C : Object { }
|
||||
|
||||
Unique!C uc = unique!C();
|
||||
Unique!Object uo = move(uc);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Allows safe construction of $(D Unique). It creates the resource and
|
||||
guarantees unique ownership of it (unless $(D T) publishes aliases of
|
||||
$(D this)).
|
||||
|
||||
Note: Nested classes and structs cannot be created at present time,
|
||||
as there is no way to transfer the closure's frame pointer
|
||||
into this function.
|
||||
|
||||
Params:
|
||||
args = Arguments to pass to $(D T)'s constructor.
|
||||
|
||||
*/
|
||||
Unique!T unique(T, A...)(auto ref A args)
|
||||
if (__traits(compiles, new T(args)))
|
||||
{
|
||||
debug(Unique) writeln("Unique.create for ", T.stringof);
|
||||
|
||||
import core.memory : GC;
|
||||
import core.stdc.stdlib : malloc;
|
||||
import std.conv : emplace;
|
||||
import core.exception : onOutOfMemoryError;
|
||||
|
||||
debug(Unique) writeln("Unique.create for ", T.stringof);
|
||||
Unique!T u;
|
||||
|
||||
// TODO: May need to fix alignment?
|
||||
// Does emplace still need to mess with alignment if
|
||||
// the memory is coming from malloc, or does malloc handle that?
|
||||
|
||||
static if (is(T == class))
|
||||
immutable size_t allocSize = __traits(classInstanceSize, T);
|
||||
else
|
||||
immutable size_t allocSize = T.sizeof;
|
||||
|
||||
void* rawMemory = malloc(allocSize);
|
||||
if (!rawMemory)
|
||||
onOutOfMemoryError();
|
||||
|
||||
static if (is(T == class)) {
|
||||
u._p = emplace!T(rawMemory[0 .. allocSize], args);
|
||||
}
|
||||
else {
|
||||
u._p = cast(T*)rawMemory;
|
||||
emplace!T(u._p, args);
|
||||
}
|
||||
|
||||
static if (hasIndirections!T)
|
||||
GC.addRange(rawMemory, allocSize);
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
struct S { }
|
||||
auto u = unique!S();
|
||||
assert(!u.empty());
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
// Some real simple stuff
|
||||
static struct S
|
||||
{
|
||||
int i;
|
||||
this(int i) { this.i = i; }
|
||||
}
|
||||
|
||||
// Some quick tests around alias this
|
||||
auto u = unique!S(42);
|
||||
assert(u.i == 42);
|
||||
assert(!u.empty);
|
||||
u.destroy();
|
||||
assert(u.empty);
|
||||
assert(!u); // Since null pointers coerce to false
|
||||
|
||||
auto i = unique!int(25);
|
||||
assert(i.get() == 25);
|
||||
assert(i == 25);
|
||||
|
||||
// opAssign still kicks in, preventing this from compiling:
|
||||
// i = null;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
static struct S
|
||||
|
@ -186,88 +288,95 @@ unittest
|
|||
int i;
|
||||
this(int i){this.i = i;}
|
||||
}
|
||||
|
||||
// Test implicit return from a function
|
||||
Unique!S produce()
|
||||
{
|
||||
// Construct a unique instance of S on the heap
|
||||
Unique!S ut = new S(5);
|
||||
Unique!S ut = unique!S(5);
|
||||
// Implicit transfer of ownership
|
||||
return ut;
|
||||
}
|
||||
|
||||
// Borrow a unique resource by ref
|
||||
// Note that references to Unique should not be passed around to
|
||||
// code that does not play a role in the Unique's life cycle.
|
||||
// (This is what .get() is for)
|
||||
void increment(ref Unique!S ur)
|
||||
{
|
||||
ur.i++;
|
||||
}
|
||||
|
||||
// See above
|
||||
void correctIncrement(ref S r)
|
||||
{
|
||||
r.i++;
|
||||
}
|
||||
|
||||
void consume(Unique!S u2)
|
||||
{
|
||||
assert(u2.i == 6);
|
||||
assert(u2.i == 8);
|
||||
// Resource automatically deleted here
|
||||
}
|
||||
|
||||
Unique!S u1;
|
||||
assert(u1.isEmpty);
|
||||
assert(!u1);
|
||||
u1 = produce();
|
||||
increment(u1);
|
||||
assert(u1.i == 6);
|
||||
//consume(u1); // Error: u1 is not copyable
|
||||
correctIncrement(u1.get());
|
||||
// yay alias this
|
||||
correctIncrement(u1);
|
||||
assert(u1.i == 8);
|
||||
|
||||
// consume(u1); // Error: u1 is not copyable
|
||||
|
||||
// Transfer ownership of the resource
|
||||
consume(u1.release);
|
||||
assert(u1.isEmpty);
|
||||
import std.algorithm : move;
|
||||
consume(move(u1));
|
||||
assert(!u1);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
// test conversion to base ref
|
||||
int deleted = 0;
|
||||
class C
|
||||
{
|
||||
~this(){deleted++;}
|
||||
}
|
||||
// constructor conversion
|
||||
Unique!Object u = Unique!C(new C);
|
||||
static assert(!__traits(compiles, {u = new C;}));
|
||||
assert(!u.isEmpty);
|
||||
destroy(u);
|
||||
assert(deleted == 1);
|
||||
// FIXME: Isn't this a bit redundant?
|
||||
// I believe all of these bases are covered in the tests above.
|
||||
|
||||
Unique!C uc = new C;
|
||||
static assert(!__traits(compiles, {Unique!Object uo = uc;}));
|
||||
Unique!Object uo = new C;
|
||||
// opAssign conversion, deleting uo resource first
|
||||
uo = uc.release;
|
||||
assert(uc.isEmpty);
|
||||
assert(!uo.isEmpty);
|
||||
assert(deleted == 2);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
debug(Unique) writeln("Unique class");
|
||||
class Bar
|
||||
static class Bar
|
||||
{
|
||||
~this() { debug(Unique) writeln(" Bar destructor"); }
|
||||
int val() const { return 4; }
|
||||
}
|
||||
|
||||
alias UBar = Unique!(Bar);
|
||||
|
||||
UBar g(UBar u)
|
||||
{
|
||||
import std.algorithm : move;
|
||||
debug(Unique) writeln("inside g");
|
||||
return u.release;
|
||||
return move(u);
|
||||
}
|
||||
auto ub = UBar(new Bar);
|
||||
assert(!ub.isEmpty);
|
||||
|
||||
auto ub = unique!Bar();
|
||||
assert(ub);
|
||||
assert(ub.val == 4);
|
||||
static assert(!__traits(compiles, {auto ub3 = g(ub);}));
|
||||
|
||||
import std.algorithm : move;
|
||||
debug(Unique) writeln("Calling g");
|
||||
auto ub2 = g(ub.release);
|
||||
auto ub2 = g(move(ub));
|
||||
debug(Unique) writeln("Returned from g");
|
||||
assert(ub.isEmpty);
|
||||
assert(!ub2.isEmpty);
|
||||
assert(!ub);
|
||||
assert(ub2);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
// Same as above, but for a struct
|
||||
import std.algorithm : move;
|
||||
|
||||
debug(Unique) writeln("Unique struct");
|
||||
struct Foo
|
||||
static struct Foo
|
||||
{
|
||||
~this() { debug(Unique) writeln(" Foo destructor"); }
|
||||
int val() const { return 3; }
|
||||
|
@ -277,18 +386,17 @@ unittest
|
|||
UFoo f(UFoo u)
|
||||
{
|
||||
debug(Unique) writeln("inside f");
|
||||
return u.release;
|
||||
return move(u);
|
||||
}
|
||||
|
||||
auto uf = UFoo(new Foo);
|
||||
assert(!uf.isEmpty);
|
||||
auto uf = unique!Foo();
|
||||
assert(uf);
|
||||
assert(uf.val == 3);
|
||||
static assert(!__traits(compiles, {auto uf3 = f(uf);}));
|
||||
debug(Unique) writeln("Unique struct: calling f");
|
||||
auto uf2 = f(uf.release);
|
||||
auto uf2 = f(move(uf));
|
||||
debug(Unique) writeln("Unique struct: returned from f");
|
||||
assert(uf.isEmpty);
|
||||
assert(!uf2.isEmpty);
|
||||
assert(!uf);
|
||||
assert(uf2);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue