* SG, on 18.08.2010 04:37:
If you're referring to the replacement of
mutable bool isCacheValid;
with
// "move-aware smart bool"
mutable replace_on_move<bool,false> isCacheValid;
in Scott's example, then yes.
If you include "prohibit automatic generation of move ops" in your
list, I don't see what the problem is with classes that don't "have a
natural empty state".
I didn't mean to include "prohibit..." for the last para but I wrote it. Thanks.
As for reasonably restricting automatic move ops generation, how about
avoiding them if there is *any* user-declared special function? So, in
the presence of a user-declared copy/move assignemt operator OR
destructor OR copy/move ctor, none of the (remaining) move ops are
generated implicitly. That catches at least Scott's and your TicTacToe
example. And by Howards analysis -- that is, we only expect
destruction and assignment (if available) to work on "zombies" -- this
should be sufficient. I think that using the existence of user-
declared (non copy/move) constructors as a kind of "invariant
detector" (like you suggested) would be too restrictive and
unnecessarily disable move ops for most "aggregate-like" classes.
That's pretty smart!
It prohibits pull-the-rug-optimization for the cases where well-behaved C++0x
code might do things not anticipated by the C++98 code, namely, after an object
has been moved from,
* being copy assigned to (executes user operator=)
* being destroyed (executes user destructor)
One question I'm not entirely sure of, is
* being copied from via copy constructor (executes user copy constructor)
part of the list of things that well-behaved C++0x code might do with a
moved-from object? I think this list of well-defined operations should be
delineated by the standard. As I see it copying from is not part of that list,
because the object might just have a minimal invariant established, sufficient
to support destruction and nothing more.
And if being copied from is not part of that list of possible operations then
whatever a user-defined copy constructor does (e.g. setting a pointer member to
point inside the object itself) cannot affect an operation O that in
well-behaved code follow after a move-from invocation of an automatically
generated move op M, because O would have to be a user defined destructor or
user defined assignment, in which case there would be no M.
I.e., if my thinking here is correct, under the rules of prohibiting automatic
move op generation when there's user defined destructor or copy assignment,
there should be no need to also prohibit for user defined copy constructor?
Just to fill out the picture, dotting the i's, AFAICS your rules work also for a
class that just has a sub-object that has a user-defined assignment or
destruction, and so on recursively.
Also, crossing the t's, it would be "non-trivial" user defined destructor.
Because many people (including myself) define destructors for other purposes
than directly customizing destruction, e.g. to make a class polymorphic. Or just
to follow a common coding convention. This then yields a little problem when the
class definition only declares the destructor and it's defined as trivial in a
separately compiled file, so I think the wording would have to refer to the
class definition, that move ops are only generated automatically when it's
/known from the class definition only/ that no non-trivial destructor is defined
(i.e. the class definition has no user defined destructor or a destructor is
defined with empty body in the class definition).
Finally, regarding previous thinking about this, In N3053 Bjarne Stroustrup &
Lawrence Crawl wrote, as 1 point of 4, "Having unnecessarily different rules for
when to generate moves and copies would cause surprising behavior."
However, in this thread examples have been presented, by Scott Meyers and me,
that show that having the particular identical-for-copy-and-move rules that were
adopted in the N3090 draft standard, cause surprising behavior, namely incorrect
Undefined Behavior, by unexpectedly invalidating class invariants in C++98 code.
Thus, AISI the rule you propose is not "unnecessarily different", and not in
conflict in with N3090. It's very /necessarily/ different.
Cheers,
- Alf (hoping I'm using the word "trivial" in the right sense here)