mirror of
https://github.com/dlang/phobos.git
synced 2025-04-29 22:50:38 +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;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue