On Saturday, 13 February 2016 at 12:14:43 UTC, Ola Fosheim Grøstad wrote:
On Saturday, 13 February 2016 at 09:11:06 UTC, Lars T. Kyllingstad wrote:
In my experience, in the vast majority of cases a C++ move operation boils down to a memberwise copy (of value types) or copy-and-reset (of reference types). With the extra logic and program flow that is sometimes involved in move construction and move assignment, I suspect that a straightforward double memcpy as it is done in D will be almost as performant or moreso most of the time.

No? Not in the libraries I write. A move typically just means copying 1-4 64 bit values and setting a single 64 bit value in the source. Usually written as an inline function.

Not knowing anything about the libraries you write, it's hard to argue with that. But I agree that given that you are in control of all the code AND can make the move ctor/assignment available for inlining (AND are an experienced programmer), then yes, you can most definitely get better performance with C++'s move() than with D's move().

But consider the more general case where you have an object of type 'struct A', which is embedded in an object of type 'struct B', which is again embedded in an object of type 'struct C', and so on, and where A, B, and C are perhaps in separate libraries or for some other reason their move ctors/assigments cannot be inlined. Then, you are looking at multiple levels of function calls and you are also at the mercy of whoever wrote their move code.

In D the cost of a move is very predictable and should be performant enough for most use cases. And for the ones where it absolutely isn't, I'm sure making a custom solution is feasible.


Or nothing, in the case where the logic does not end up with a move, something which D cannot represent with the same semantic distinction.

Add to that the fact that a lot of programmers out there will implement move construction in terms of move assignment -- which makes it a default construction PLUS move -- and move assignment in terms of swap -- i.e., three moves -- for the sake of DRY.

Huh? Move assignments is no different from move construction, except you have to release the existing value if the receiving object isn't empty. Constructing an empty resource owner object usually just means setting a single field to zero, which is inlined and removed if it is followed by an assignment.

What I meant is that you will find a lot of C++ code out there, written by well-meaning programmers, that looks like this:

class C
{
    C(C&& other)
    {
        operator=(std::move(other));
    }
    // and/or
    C& operator=(C&& other)
    {
        swap(*this, other);
        return *this;
    }
};

Here, you have unnecessary construction of C's members in the constructor which may or may not be optimised away before the assignment. Furthermore, you have an unnecessary number of moves in the assignment operator -- plus the potential drawbacks of deferred release of the resource.

Personally, I think D's move semantics are actually clearer and easier to get right.

But I don't think D has move semantics. I don't think it makes for correctness for resource ownership.

I'm not sure what you mean by "has move semantics" here. It does not have C++'s move semantics, no, but I would say D has its own move semantics. It has a move() function that transfers raw state between objects, and D structs are supposed to be designed so they are movable by means of raw bit transfer, allowing the compiler and GC to move them around as it sees fit. But maybe I'm missing something?


explain to newbies: If you use std.move() on something it definitely gets moved. In C++, if you use std::move() on something it may or may not be moved; it depends on the recipient of the move.

No? With D's std.move() the resource can be destroyed or get into an inconsistent state if the caller does it wrong?

I guess this is what I don't understand. How and when does that happen?

Lars

Reply via email to