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

Reply via email to