February 01, 2016 D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Hi all, I have a non-copyable struct with move semantics. In other words, a struct with @disable this(this), but with working overloads for the this(copy) and opAssign. Now I have an instance of that struct. I would like to be able to voluntarily give up ownership for the sake of another instance. In C++, I would do something like this: unique_ptr<int> p (new int), q; q = std::move(p); I am unsure what is the correct way to do this under D. Shachar |
February 02, 2016 Re: D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Shachar Shemesh | On 02/02/16 2:21 AM, Shachar Shemesh wrote:
> Hi all,
>
> I have a non-copyable struct with move semantics. In other words, a
> struct with @disable this(this), but with working overloads for the
> this(copy) and opAssign.
>
> Now I have an instance of that struct. I would like to be able to
> voluntarily give up ownership for the sake of another instance.
>
> In C++, I would do something like this:
>
> unique_ptr<int> p (new int), q;
>
> q = std::move(p);
>
> I am unsure what is the correct way to do this under D.
>
> Shachar
So just to confirm, you want to explicitly copy a struct but not "implicitly" copy it?
struct Foo {
@disable
this(this);
int x;
Foo dup() {
return Foo(x);
}
}
void main() {
Foo a, b;
a = Foo(7);
b = a.dup;
// will error
// b = a;
}
Please note for this example code I have implemented dup, you wouldn't normally need to do that for structs.
Also D.learn is correct place to ask this. No the general N.G.
|
February 01, 2016 Re: D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Shachar Shemesh | Am 01.02.2016 um 14:21 schrieb Shachar Shemesh: > Hi all, > > I have a non-copyable struct with move semantics. In other words, a > struct with @disable this(this), but with working overloads for the > this(copy) and opAssign. > > Now I have an instance of that struct. I would like to be able to > voluntarily give up ownership for the sake of another instance. > > In C++, I would do something like this: > > unique_ptr<int> p (new int), q; > > q = std::move(p); > > I am unsure what is the correct way to do this under D. > > Shachar Should work with move() from std.algorithm: http://dlang.org/library/std/algorithm/mutation/move.html |
February 01, 2016 Re: D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Shachar Shemesh | On Monday, 1 February 2016 at 13:21:02 UTC, Shachar Shemesh wrote:
> Hi all,
>
> I have a non-copyable struct with move semantics. In other words, a struct with @disable this(this), but with working overloads for the this(copy) and opAssign.
>
> Now I have an instance of that struct. I would like to be able to voluntarily give up ownership for the sake of another instance.
auto move (T) (ref T origin)
if (is(T == struct))
{
scope(exit) origin = T.init;
return T(origin.tupleof);
}
// example:
struct S
{
int* x;
@disable this(this);
}
void foo (S s) { }
void main()
{
auto s = S(new int);
// won't compile, postblit is disabled:
foo(s);
// but this works, because rvalues are always moved:
foo(move(s));
assert(s.x is null);
}
|
February 01, 2016 Re: D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Shachar Shemesh | On Monday, 1 February 2016 at 13:21:02 UTC, Shachar Shemesh wrote:
> q = std::move(p);
>
> I am unsure what is the correct way to do this under D.
Note that C++ std::move(...) doesn't do anything related to state, it is only a type cast, so it is zero-overhead.
What I have done to get semantics close to C++ is to define "moving" type and a "pointer" type that use it.
(I dislike how D deals with this.)
|
February 02, 2016 Re: D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ola Fosheim Grøstad | On Monday, 1 February 2016 at 13:52:49 UTC, Ola Fosheim Grøstad wrote:
> On Monday, 1 February 2016 at 13:21:02 UTC, Shachar Shemesh wrote:
>> q = std::move(p);
>>
>> I am unsure what is the correct way to do this under D.
>
> Note that C++ std::move(...) doesn't do anything related to state, it is only a type cast, so it is zero-overhead.
>
> What I have done to get semantics close to C++ is to define "moving" type and a "pointer" type that use it.
>
> (I dislike how D deals with this.)
Could you share this? I would be very interested in how you have approached it.
|
February 02, 2016 Re: D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Shachar Shemesh | On 02/01/2016 05:21 AM, Shachar Shemesh wrote: > I have a non-copyable struct with move semantics. In other words, a > struct with @disable this(this), but with working overloads for the > this(copy) and opAssign. > > Now I have an instance of that struct. I would like to be able to > voluntarily give up ownership for the sake of another instance. > > In C++, I would do something like this: > > unique_ptr<int> p (new int), q; > > q = std::move(p); > > I am unsure what is the correct way to do this under D. This question has been brought up a lot lately. I've decided to look at this more seriously yesterday. My first observation is that if @post-blit is disabled isn't the struct already a unique type? If so, we don't need unique_ptr for variables of that type, right? Am I completely off there? vector<unique_ptr<T>> is a valid use case but it's not possible with D's arrays because D's arrays work with .init object, which require assigning into. So, I've played with an array type that can store non-copyable types. The main functions are emplace_back() and move_back(). One requirement is that the stored type must be idempotent regarding destruction of its .init value. Have you used something similar before? Is this a correct approach to this problem? (Pardon the non-D naming convention that uses underscores.) /* An array that can store non-copyable types. */ struct UniqueArray(T) { ubyte[] storage; size_t count; @disable this(this); this(size_t size) { storage = new ubyte[](size); } ~this() { foreach (i; 0 .. count) { destroy_at(i); } } /* Returns the address of element at index 'i'.*/ T* addressOf(size_t i) { const offset = T.sizeof * i; return cast(T*)(storage.ptr + offset); } /* Returns a range to all elements. TODO: Use operator overloading. */ auto all() { auto arr = (cast(T*)(storage.ptr))[0..count]; T*[] result; foreach (ref i; arr) { result ~= &i; } return result; } import std.typecons : Flag, Yes, No; /* Emplaces a new object at index 'i' with the given constructor * arguments. */ T* emplace_at(Flag!"occupied" occupied = Yes.occupied, Args...)(size_t i, Args args) { import std.exception : enforce; import std.conv : emplace; enforce(i <= count); T* place = addressOf(i); /* TODO: Be exception-safe; don't destroy before succesful * construction. */ if (occupied) { destroy_at(i); } emplace(place, args); return place; } /* Emplaces an object at the end with the given constructor arguments. */ T* emplace_back(Args...)(Args args) { if (storage.length == (count * T.sizeof)) { storage.length += T.sizeof; } const isOccupied = false; T* place = emplace_at!(No.occupied)(count, args); ++count; return place; } /* Moves an lvalue to the end. The arguments becomes T.init. */ void move_back(ref T s) { import std.algorithm : move; T* place = emplace_back(); move(s, *place); } /* Destroys the element at index 'i'. */ void destroy_at(size_t i) { destroy(*addressOf(i)); } } UniqueArray!S uniqueArray(S)(size_t size = 0) { return UniqueArray!S(size); } /* Test code follows. */ import std.stdio; /* A non-copyable type. */ struct S { int i = -1; @disable this(this); void printInfo(string func = __FUNCTION__)() { writefln("%s for %s", func, i); } this(int i) { this.i = i; printInfo(); } ~this() { printInfo(); if (i == -1) { /* This type does not do anything special for its .init value. */ writefln(" ... (Skipping cleanup for .init)"); } else { writefln(" Doing proper cleanup"); } } } void main() { auto u = uniqueArray!S(); /* Some are emplaced back, some are moved back. */ foreach (i; 0 .. 5) { if (i % 2) { writefln("Emplacing rvalue %s back", i); u.emplace_back(i); } else { writefln("Making lvalue %s", i); auto s = S(i); writefln("Moving lvalue %s back", i); u.move_back(s); assert(s == S.init); } } writefln("Replacing 2 with 100"); u.emplace_at(2, 100); writefln("Destroying %s", 1); u.destroy_at(1); /* Just do someting with element states. NOTE: writeln(u) does not work * because 'u' is not copyable. */ foreach (i, ref e; u.all) { writefln("%s: %s", i, e.i); } writefln("Leaving main"); } For convenience, here is the output of the program: Making lvalue 0 deneme.S.this for 0 Moving lvalue 0 back deneme.S.~this for -1 ... (Skipping cleanup for .init) deneme.S.~this for -1 ... (Skipping cleanup for .init) Emplacing rvalue 1 back deneme.S.this for 1 Making lvalue 2 deneme.S.this for 2 Moving lvalue 2 back deneme.S.~this for -1 ... (Skipping cleanup for .init) deneme.S.~this for -1 ... (Skipping cleanup for .init) Emplacing rvalue 3 back deneme.S.this for 3 Making lvalue 4 deneme.S.this for 4 Moving lvalue 4 back deneme.S.~this for -1 ... (Skipping cleanup for .init) deneme.S.~this for -1 ... (Skipping cleanup for .init) Replacing 2 with 100 deneme.S.~this for 2 Doing proper cleanup deneme.S.this for 100 Destroying 1 deneme.S.~this for 1 Doing proper cleanup 0: 0 1: -1 2: 100 3: 3 4: 4 Leaving main deneme.S.~this for 0 Doing proper cleanup deneme.S.~this for -1 ... (Skipping cleanup for .init) deneme.S.~this for 100 Doing proper cleanup deneme.S.~this for 3 Doing proper cleanup deneme.S.~this for 4 Doing proper cleanup Ali |
February 02, 2016 Re: D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Posted in reply to maik klein | On Tuesday, 2 February 2016 at 21:18:27 UTC, maik klein wrote:
> Could you share this? I would be very interested in how you have approached it.
My ad hoc pointer experiments try to be general and is convoluted, so not the best example. The basic idea is to have a pointer type that match "&&" in C++ (which cannot be done completely).
So we have something like:
struct moving(T){
T* ptr;
~this(){ assert(ptr is null); } // optional check to ensure that the move is completed
}
then have a move() function that returns a moving!T struct
In you pointer struct you match opAssign(moving!T ...) to emulate "operator=(T&& ...)" and set the ptr field to null to prevent it from being used again (if you want that extra check).
|
February 02, 2016 Re: D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ali Çehreli | On Tuesday, 2 February 2016 at 22:36:22 UTC, Ali Çehreli wrote:
> This question has been brought up a lot lately. I've decided to look at this more seriously yesterday.
Nice to see that others are playing around with this, I don't have time to check your code, but one key issue with move semantics is exception safety.
AFAICT D's "std.move" is insufficient, as it would null out the original pointer prematurely and when an exception is thrown the resource will disappear rather than simple remain "unmoved".
|
February 02, 2016 Re: D's equivalent to C++'s std::move? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ola Fosheim Grøstad | On 02/02/2016 03:09 PM, Ola Fosheim Grøstad wrote:
> On Tuesday, 2 February 2016 at 22:36:22 UTC, Ali Çehreli wrote:
>> This question has been brought up a lot lately. I've decided to look
>> at this more seriously yesterday.
>
> Nice to see that others are playing around with this, I don't have time
> to check your code, but one key issue with move semantics is exception
> safety.
>
> AFAICT D's "std.move" is insufficient, as it would null out the original
> pointer prematurely and when an exception is thrown the resource will
> disappear rather than simple remain "unmoved".
>
Exactly. I've saved my rear end by inserting a TODO comment just before posting the code: :p
/* TODO: Be exception-safe; don't destroy before succesful
* construction. */
if (occupied) {
destroy_at(i);
}
emplace(place, args);
Ali
|
Copyright © 1999-2021 by the D Language Foundation