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;
/**
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);
}