Merge pull request #3139 from mrkline/better-unique

Improve std.typecons.Unique
This commit is contained in:
Martin Nowak 2015-04-24 20:24:18 +02:00
commit 8f4a85bc83

View file

@ -50,11 +50,16 @@ import std.typetuple; // : TypeTuple, allSatisfy;
debug(Unique) import std.stdio; debug(Unique) import std.stdio;
/** /**
Encapsulates unique ownership of a resource. Resource of type $(D T) is Encapsulates unique ownership of a resource.
deleted at the end of the scope, unless it is transferred. The
transfer can be explicit, by calling $(D release), or implicit, when Like C++'s $(LINK2 http://en.cppreference.com/w/cpp/memory/unique_ptr, std::unique_ptr),
returning Unique from a function. The resource can be a polymorphic a $(D Unique) maintains sole ownership of a given resource of type $(D T) until
class object, in which case Unique behaves polymorphically too. 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) struct Unique(T)
{ {
@ -64,67 +69,18 @@ static if (is(T:Object))
else else
alias RefT = T*; 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. 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 Typically used to transfer a $(D Unique) rvalue of derived type to
a $(D Unique) of base type. a $(D Unique) of base type.
Example: Example:
--- ---
class C : Object {} class C : Object { }
Unique!C uc = new C; Unique!C uc = unique!C();
Unique!Object uo = uc.release; Unique!Object uo = move(uc);
--- ---
*/ */
this(U)(Unique!U u) this(U)(Unique!U u)
@ -146,39 +102,185 @@ public:
u._p = null; u._p = null;
} }
/// Destroying a $(D Unique) frees the underlying resource.
~this() ~this()
{ {
import core.stdc.stdlib : free;
debug(Unique) writeln("Unique destructor of ", (_p is null)? null: _p); debug(Unique) writeln("Unique destructor of ", (_p is null)? null: _p);
if (_p !is null) delete _p; if (_p !is null)
_p = 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 /// Transfer ownership to a $(D Unique) rvalue.
{ deprecated("Please use std.algorithm.move to transfer ownership.")
return _p is null;
}
/** Transfer ownership to a $(D Unique) rvalue. Nullifies the current contents. */
Unique release() Unique release()
{ {
import std.algorithm : move;
debug(Unique) writeln("Release"); debug(Unique) writeln("Release");
auto u = Unique(_p); Unique u = move(this);
assert(_p is null); assert(_p is null);
debug(Unique) writeln("return from Release"); debug(Unique) writeln("return from Release");
return u; 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); @disable this(this);
private: private:
RefT _p; 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 unittest
{ {
static struct S static struct S
@ -186,88 +288,95 @@ unittest
int i; int i;
this(int i){this.i = i;} this(int i){this.i = i;}
} }
// Test implicit return from a function
Unique!S produce() Unique!S produce()
{ {
// Construct a unique instance of S on the heap // 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 // Implicit transfer of ownership
return ut; return ut;
} }
// Borrow a unique resource by ref // 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) void increment(ref Unique!S ur)
{ {
ur.i++; ur.i++;
} }
// See above
void correctIncrement(ref S r)
{
r.i++;
}
void consume(Unique!S u2) void consume(Unique!S u2)
{ {
assert(u2.i == 6); assert(u2.i == 8);
// Resource automatically deleted here // Resource automatically deleted here
} }
Unique!S u1; Unique!S u1;
assert(u1.isEmpty); assert(!u1);
u1 = produce(); u1 = produce();
increment(u1); increment(u1);
assert(u1.i == 6); 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 // Transfer ownership of the resource
consume(u1.release); import std.algorithm : move;
assert(u1.isEmpty); consume(move(u1));
assert(!u1);
} }
unittest unittest
{ {
// test conversion to base ref // FIXME: Isn't this a bit redundant?
int deleted = 0; // I believe all of these bases are covered in the tests above.
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);
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"); debug(Unique) writeln("Unique class");
class Bar static class Bar
{ {
~this() { debug(Unique) writeln(" Bar destructor"); } ~this() { debug(Unique) writeln(" Bar destructor"); }
int val() const { return 4; } int val() const { return 4; }
} }
alias UBar = Unique!(Bar); alias UBar = Unique!(Bar);
UBar g(UBar u) UBar g(UBar u)
{ {
import std.algorithm : move;
debug(Unique) writeln("inside g"); 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); assert(ub.val == 4);
static assert(!__traits(compiles, {auto ub3 = g(ub);}));
import std.algorithm : move;
debug(Unique) writeln("Calling g"); debug(Unique) writeln("Calling g");
auto ub2 = g(ub.release); auto ub2 = g(move(ub));
debug(Unique) writeln("Returned from g"); debug(Unique) writeln("Returned from g");
assert(ub.isEmpty); assert(!ub);
assert(!ub2.isEmpty); assert(ub2);
} }
unittest unittest
{ {
// Same as above, but for a struct
import std.algorithm : move;
debug(Unique) writeln("Unique struct"); debug(Unique) writeln("Unique struct");
struct Foo static struct Foo
{ {
~this() { debug(Unique) writeln(" Foo destructor"); } ~this() { debug(Unique) writeln(" Foo destructor"); }
int val() const { return 3; } int val() const { return 3; }
@ -277,18 +386,17 @@ unittest
UFoo f(UFoo u) UFoo f(UFoo u)
{ {
debug(Unique) writeln("inside f"); debug(Unique) writeln("inside f");
return u.release; return move(u);
} }
auto uf = UFoo(new Foo); auto uf = unique!Foo();
assert(!uf.isEmpty); assert(uf);
assert(uf.val == 3); assert(uf.val == 3);
static assert(!__traits(compiles, {auto uf3 = f(uf);}));
debug(Unique) writeln("Unique struct: calling f"); 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"); debug(Unique) writeln("Unique struct: returned from f");
assert(uf.isEmpty); assert(!uf);
assert(!uf2.isEmpty); assert(uf2);
} }