
I think this comes down to my poor definition of use. I was thinking of
"use" as apply public methods and mentally classifying assignment, copying, destruction as operations preparing objects for "use". I am somewhat confused about the move-from standardisation and will attempt to study this in more detail. My perception of this is that the object is in a rather new state and if I can't call public accessors and therefore their relationships no longer hold I think it could be argued that the class-invariants have been broken.
You could define it that way, but then you lose any power to use the invariant to reason about program correctness. Again, for class invariants to have any power, they have to never break except during individual mutations.
This is my the root of my concern. If I understand correctly we now have a new "state" of an object which isn't related to any of it's attributes during its lifetime. And this becomes an implicit part of preconditions to functions. However unlike preconditions in languages like Eiffel, I cannot test to see to determine if the precondition is valid. I have tried to read around the C++ specification but I'm finding it hard to comprehend.
The way you deal with this situation is to say that those "public accessors" (or whatever) have preconditions that include "not in the moved-from state."
It is interestingly divergent from the typical Design by Contract idiom. It is much more usual, in my experience for the class-invariants to hold up to and including entry to the destructor
I don't understand what any of that means.
I'm sorry for not being clearer. From my understanding of DbC the class-invariants must hold upon entry and exit of public functions with the exceptions that the class-invariants may not hold upon entry into the constructors and upon exit of the destructor.
Yeah, well the object doesn't exist in those periods, so I just add
"during the object's lifetime" and the definition is complete.
I agree that this is a good definition and appears consistent with other languages with which I am familiar. I was under the misapprehension that the object being moved was being put into a new state which did not satisfy the class-invariants. I go this impression from reading earlier entries in this thread. This is clearly not the case as you have clarified below. I now think I understand that an object that has been moved has its state altered such that the precondition of all functions may now be invalid except for the assignment operator and the destructor. Is this correct? I'm really sorry for being so slow to comprehend this. I seem to be being rather dim.
Since I can no longer operate upon the object after it has been moved
Who says you can no longer operate upon the object after it has been moved?
it seems we have a new "undefined state" that doesn't gel well with
typical DbC application.
No, it's just a state. The moved-from state for std::vector is the same as its empty state. You can do all the same things with it.
Ah ha! Thank you this was my central misunderstanding from reading a mix of information.
As I stated previously I don't label this as bad. I label it as interesting.
It's not as interesting as you're making it out to be, or at least it doesn't have to be. Creating "interesting" exceptions just makes it harder to reason about code. There's a coherent point of view where the usual definition of "invariant" holds, that fully describes the situation. Why not adopt that point of view instead of changing the meaning of "invariant?"
I now understand. I certainly did not want to have an unjustified update to my mental model without justification. I'm happy that the move support does not require any such adjustment.
Incidentally, this is the same reason the basic exception-safety guarantee doesn't say "the object needs to be destructible, but its invariants don't necessarily hold." There's no need to say that provided you correctly describe the invariant.
Exactly, since the class-invariant holds upon entry of the destructor. The world appears consistent once more :-)
If I understand correctly the standard is divergent
divergent from what?
My experience of DbC with Eiffel, D. I was simply referring to the odd state of the object where it is still 'live' yet cannot have public functions called despite the caller meeting pre-conditions.
The state of the object in question is one of the preconditions. Can I write v.front()? Not if the v is an empty container. There's no difference.
Yes, I see that now that the move simply alters state that affects pre-conditions. I assume the pre-condition is always query-able for all of the affect standard library types; otherwise that would be a subtle difference. The standard has a requirement for implementers of move constructors that the post-condition of the move operation leaves the moved-from object in a state whereby the precondition for assignment and the destructor are true. It is actually as simple as that.
This seems to require an adjustment to the way one reasons about code albeit in a not particularly troublesome way.
Aù contraire, I would find that "adjustment" extremely troubling, which is why I don't go there.
The requirement for an adjustment was worrying me and my motivation for embarrassing myself by disclosing my confusion!
to allow sensible compiler implementations of clean-up code for moved-from objects.
I don't understand what you're saying here, either.
I think I'm probably wasting your time. I should probably attempt to improve my understanding of the currently specified move semantics.
IMO you should attempt to adopt a way of explaining those semantics to yourself that doesn't require tearing asunder fundamental definitions, like "invariant," upon which we rely to understand code. :-)
I think while I do not understand the Rationale for allowing
assignment and destruction of moved-from objects
Destruction is trivial to explain:
void f(bool q, X a) { std::vector<X> v; if (q) v.push_back(std::move(a)); // <=== What happens to a right here? }
Remember that q and the set of things potentially moved-from can be arbitrarily complex, so although the above example could be handled easily in a compiler's codegen, in general it can't.
I imagine the main problem is that we might need to traverse function implementations in other translation units that may not be available to complete the analysis.
The reasons for assignment are in part practical. The complexity of vector::insert explodes when you support move semantics but can't assign to moved-from elements. You need at least two implementations in that case.
Thanks for the guidance. I struggled to find an example that explained why assignment was treated specially. This really helped. I really didn't grok the non-destructive part of the non-destructive move.
I am not in a position to make intelligent comments. I try to make that stop me from posting; sometimes I suffer the delusion that I actually understand things!
I think you're understanding move semantics, but you understand them in a way that is harmful to your understanding of other things. I suggest you pick an understanding that's consistent with the other theories. ;-)
Actually no, my misunderstand was definitely with the move semantics. My grounding in DbC is solid, having been involved with several popular implementations.
-- Dave Abrahams BoostPro Computing Software Development Training http://www.boostpro.com Clang/LLVM/EDG Compilers C++ Boost
Thank you for taking the considerable time to help clarify my understanding. It has been extremely useful to me. I couldn't get this clear no matter how hard I tried from reading material alone. Thanks, Neil Groves