On Saturday, 13 February 2016 at 22:42:34 UTC, Ola Fosheim Grøstad wrote:
On Saturday, 13 February 2016 at 21:41:06 UTC, Lars T. Kyllingstad wrote:
Whose expectations? The formal expectation, as per the C++ standard, is that the moved-from object be left in a "valid but unspecified state". Basically, as long as it is safe to destroy or reassign to the moved-from object, you're good.

Hmm, do you have a reference (URL) to the "valid but unspecified state" part? I'm not quite sure what that refers to.

I know you said afterwards you didn't need a reference, but I'll give you one anyway. :) That is the formal requirement for C++ standard library types; see sec. 17.6.5.15 [lib.types.movedfrom] of the C++ specification.

But I agree that, for the most part, one would expect that the moved-from object holds *no* resource and that the resource previously held by the target object has been released.


I hope this is not coming across as me endorsing the practice of implementing move assignment in terms of swap, because I don't. But it *is* a rather common practice, enough so that Scott Meyers felt the need to write an article about it:

I've never heard of it, and never thought it would be a good idea. Are you sure this is common?

Pretty sure, but off the top of my head I can't give you too many concrete examples beyond the Meyers article I linked to, Stack Overflow questions, and one particular well-known and respected library (ZeroMQ) where I recently ran into it.


A swap is three moves -- actual moves.

If you are talking std::swap, probably. Never use it, so don't know if there is any measurable overhead.

I was, yes.


If you are talking about the microcode in the CPU, then it typically takes 2 loads and 2 stores to swap two pointers on the heap, and the loads and stores can execute in parallel... So performance wise, not a big deal. But with debugging/consistency in mind you should set the source to nullptr instead.

I know programmers talk alot about swap being implemented as

tmp = a
a = b
b = a

But that is actually how it is specified it source code, not how it is implemented in running code. In the CPU it goes like this:

reg1 = load a; reg2 = load b
b = store reg1; a = store reg2

Setting it to null would be almost the same.

reg1 = load a; reg2 = 0
b = store reg1; a = store reg2

Unless you use special commands and zero out the entire cacheline of "a" you still get the same amount of cache-misses as well.

For your lowest-level resource owners, I guess that is the case. But if a and b are largeish compound types that don't fit in a register, that's not the case, right? Or can the optimiser deal with this in a good way too?


Well, if you have a back pointer that is part of the invariant for the type, then neither move or copy work as expected. In C++ you have the address of the source object and can either modify or change the associated data-structure that provide back pointers (e.g. a global hash with pointers to the struct, in C++ you can change these pointers to point to the new location/add another entry). AFAIK this is not possible in D without adding an indirection.

So what you're saying that a particular kind of designs/patters can not be safely combined with D's standard move mechanism. That is of course very unfortunate, but I guess it can be worked around? I thought you were implying that simply using move() on any struct could potentially mess it up...

Reply via email to