On Saturday, 13 February 2016 at 21:41:06 UTC, Lars T.
Kyllingstad wrote:
D is all about opinionated solutions. :) In fact, I would go
so far as to say that's what sets D apart from C++, and mostly
in a good way.
D1 was all about opinionated ready-made builtin solutions, but D2
is supposedly meant to support generic programming and then the
responsibility of "creating opinions" should be moved out of the
language and into libraries.
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 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?
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.
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.
What is special is D's requirement that structs be movable by a
raw bit blit, which again enables our particular library
implementation of move().
C++ has no such requirement; for example it is perfectly OK for
an on-stack C++ object to contain a pointer to itself. A
D-like move() on such an object would just produce mayhem.
Yes, but it isn't enforced by the compiler by static analysis, is
it? So D has no particular advantage to C++ for an object that
is designed to be movable.
and it is easy to understand and explain to users. In C++ you
have no idea whether the resource is lost -- it depends on when
the actual move operation happens and when the exception is
thrown.
Well, you do, if you implement exception safe RAII, which you
should. RAII ensures that it will be released when the owner
object is destructed.
Still not following you. Postblit is not involved in a move at
all -- that's what makes it a move.
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.