<code>
Y f(bool choose)
{
Y a, b;
InvariantChecker xa(a), xb(b);
// The use of ?: guarantees that there will be no copy elision.
// Instead, either a or b will be *moved* into the return value.
return choose ? a : b;
// xb and xa get destroyed here
// b and a get destroyed thereafter
}
</code>
</quote>
"Where InvariantChecker is a class whose destructor checks the
invariant of the object with which it was constructed. The return
statement breaks the invariant of either a or b, and the
InvariantChecker detects that breakage as it is being destroyed."
<quote>
As found in the comment of
http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/
Thanks, Patrik
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Unfortunately, with that code by itself I felt it was rather hard to
understand the problem. Since I just tried anyway, let me try to
summarize the problem how I understood it:
In 2010/10 [1] Dave posted how implicit move is broken in several ways.
One example looked like this:
struct X {
// invariant: v.size() == 5
X() : v(5) {}
~X()
{
std::cout << v[0] << std::endl;
}
private:
std::vector<int> v;
};
The implicitly generated move however would break the invariant, because
in the moved-from state the vector would have zero elements.
Later, the committee tightened the rules for implicitly generated
move-operations, and I'll just quote Dave [2] here:
[q]
If the class definition of a class X
does not explicitly declare a move
constructor, one will be implicitly
declared as defaulted if and only if
* X does not have a user-declared copy constructor
* X does not have a user-declared copy assignment operator,
* X does not have a user-declared move assignment operator,
* X does not have a user-declared destructor, and
* the move constructor would not be implicitly defined as deleted.
[/q]
Seeing as in the struct X above, the dtor is fully optional, moving-from
will *still* leave this class in an invalid state, as all rules are
fulfilled.
I hope I got the problem correctly.
NOW, TO MY QUESTION:
It seems to me that *not* the rules for implicit move-op generation are
broken, but instead the rules for creating the default move ctor.
If I understand correctly the default move ctor will zero-initialize all
members of the moved-from object (Correct??). This *seems wrong*, as
with the struct X above, there is -- at first -- no way to create an
instance of X that has zero-initialized members.
Adding a move-ctor to this class however, will introduce such a state to
the class, even though the default ctor does not allow for this state.
(Correct??)
Does that make sense?
cheers,
Martin
[1] Implicit Move Must Go:
http://cpp-next.com/archive/2010/10/implicit-move-must-go/
[2] Comment on W00t W00t Nix Nix!:
http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/comment-page-1/#comment-1321
There seems to be a misunderstanding. I think by "implicit move-op
generation" the author means the implicit generation of the move ctor
and not the actual invocation of it.
> If I understand correctly the default move ctor will zero-initialize all
> members of the moved-from object (Correct??).
This was part of one proposal. But it's not part of the current draft.
> Adding a move-ctor to this class however, will introduce such a state to
> the class, even though the default ctor does not allow for this state.
> (Correct??)
>
> Does that make sense?
The idea is that nobody is going to notice a difference. Of course,
Dave showed us an example where an unintentionally move-from object is
accessed beyond destruction/assignment and this turns working code in
weirdly behaving code. I appreciate the example. But I'm not 100%
convinced that this is an actual problem in "real code".
Also, notice comments by Michal Mocny. He argues that the real cause
of breaking the example given in the OP is not implicitly generated
move constructor, but it's usage of it.
Even with a user defined move-ctor that keeps the invariants,
the destructor of InvariantChecker would use a reference to
a modified (moved-from) object.
I agree with this conclusion... what's more important, Dave Abrahams
agreed to. I also believe that the last paragraph that SG wrote
actually states the same argument, but I cannot be sure...
--
Dragan
As I understand it. With the old X example, as it has a user defined
destructor no implicit move constructor will be generated with the new
rules. So it does not break, as X will never be moved only copied.
The InvariantChecker example is more interesting because Y is some
type that does not have any user defined copy constructor or
destructor so move constructor will be implicitly generated for it.
Now the non-intrusive wrapper object InvariantChecker contains a
reference to the Y object. InvariantChecker lives its entire lifetime
in the local function, the wrapped object lives on beyond the function
as it is moved (not copied) out by return. InvariantChecker expects
its reference to point to the same logical object its entire lifetime.
But actually by the time InvariantChecker gets destructed the original
logical object has just moved elsewhere, only leaving some default
state replica in its place. So when InvariantChecker in its destructor
checks the wrapped object it is no longer checking the same logical
object it expects, and the invariant validation fails.
So here is the sequence as i understand it:
1. a and b constructed
2. xa and xb constructed
3. a or be moved (leaving an empty state a or b object in its place)
4. xa and xb destructed (now checks the invariant of an empty state a
or b, so fails)
1. a and b destructed
It actually gets better, or rather worse, as Michal Mocny commented
this same problem will happen even if the Y object has a explicit user
defined move constructor. So the problem is not limited to implicitly
defined move constructors. Imagine Y is some unrelated 3rd party
object that is not aware that InvariantChecker un-intrusively wraps it
like this. So its not something Y can be expected to guard against, by
disabling its move constructors. To make things a bit simpler imagine
Y is a typedef for std::string which has its explicit move
constructor. And just imagine the invariant checking done in the
destructor of InvariantChecker is 'a == "some value that I expect this
string object to always have"'. Well at InvariantChecker destruction
time 'a' is an empty string, so invariant validation fails. This all
seems quite worrying to me, I hop I have just miss understood
something. I would be interested in what you guys make of this all.
To quote myself from a later comment on that blog post:
"[...] Implicitly returning any object with a move constructor (where
the move constructor is implicitly generated or explitly defined)
would cause the same problem in that example. Seems the example shows
an even more fundamental problem. I'm not sure where this leaves us. I
guess going forward one has to be extra careful with wrapper objects
that only contain a reference to the wrapped object. The wrapped
object may have moved elsewhere by the time the wrapper is destructed,
only leaving some default state replacement in its place. I guess one
can no longer assume one know the order that stack objects are
destructed, as some might have moved beyond the closing "}" only
leaving that very different default state object behind. Things sure
seem a bit more complicated now with move semantics."
Maybe it would be better to continue this discussion on the comment
section of the original blog. Me moving(!) it here might have been bad
manners. Then again long discussion in blog comments are hard to
follow. Your call.
Thanks, Patrik
Yes, maybe this is not so serious after all. It seems there is an easy
fix (regardless if the move constructor was implicit or explicitly
generated).
So currently (as I understand it) the sequence is
1. a and b are constructed
2. xa an xb are constructed
3. a or b is moved (destroying the invariant of the empty state object
left behind. Call them 'a or 'b)
4. xa and xb are destructed (invariant check fails for 'a or 'b)
5. a' and b' are destructed
A proposed fixed sequence would be
1. a and b are constructed
2. xa an xb are constructed
3. xa and xb are destructed (invariant check succeeds on a and b)
4.1. a or b is moved (destroying the invariant, but this is fine as no
local object can depend on our invariants at this point. Call them 'a
or 'b)
4.2. a' and b' are destructed (fine)
Here 4.1 and 4.2 belong together as one unit. The move and destructor
represent a two step destruction. So a move is really half a
destructor. And the first half of the destructor was executed before
its time. First in, and last out should applies like usual.
I'm not sure which sequence is actually specified in c++0x. But I
think it should be the second one. Anyone see any problem with this?
Thanks.
What I meant to say was: The OP suggested, that the rules are broken
which govern *when* move-ctor is implicitly generated. To me it seems -
but I am not sure I understand this fully enough to really tell - that
rather the rules on *how* the defaulted move-ctor is generated is broken.
>> If I understand correctly the default move ctor will zero-initialize all
>> members of the moved-from object (Correct??).
>
> This was part of one proposal. But it's not part of the current draft.
>
I looked it up in N3126: 12.8 - 17
The implicitly-defined move
constructor for a non-union
class X performs a memberwise
move of its subobjects. [...]
This isn't zero-initialization, but with the vector invariant example it
basically boils down to the same thing, namely that any invariants that
the default ctor sets up are not respected by the implicitly-defined
move-ctor.
>> Adding a move-ctor to this class however, will introduce such a state to
>> the class, even though the default ctor does not allow for this state.
>> (Correct??)
>>
>> Does that make sense?
>
> The idea is that nobody is going to notice a difference. Of course,
> Dave showed us an example where an unintentionally move-from object is
> accessed beyond destruction/assignment and this turns working code in
> weirdly behaving code. I appreciate the example. But I'm not 100%
> convinced that this is an actual problem in "real code".
>
Let me highlight your phrase: "move-from object is accessed beyond
destruction/assignment" --
First, the object is certainly not accessed beyond destruction, and it
certainly is an important feature that objects live *longer* than their
return statement.
Second, as far as I understand, it is still intended to be valid
behaviour to access a moved-from object, as in `x = std::move(y);
y.empty();`. While this piece of code obviously wouldn't be found in old
code, it still seems to pose a problem WRT to class invariants of
existing classes.
So to me it seems the rules for when move-ops are implicitly generated
are still a bit brittle and given how complicated they already are it
really might be for the best to just abandon the implicit generation of
move-ops.
Or to greatly tighten them to classes that do not have any user-defined
ctors -- which would still be useful, as simple aggregate structs would
then trivially have all the copy and move ops defined, which certainly
is something good.
cheers,
Martin
Ah, but the user-defined X dtor is just there as a logging means. A
"real" class would likely not have it.
> The InvariantChecker (...)
> Now the non-intrusive wrapper object InvariantChecker contains a
> reference to the Y object. InvariantChecker lives its entire lifetime
> in the local function, the wrapped object lives on beyond the function
> as it is moved (not copied) out by return. InvariantChecker expects
> its reference to point to the same logical object its entire lifetime.
> But actually by the time InvariantChecker gets destructed the original
> logical object has just moved elsewhere, only leaving some default
> state replica in its place. So when InvariantChecker in its destructor
> checks the wrapped object it is no longer checking the same logical
> object it expects, and the invariant validation fails.
>
Exactly. Except that C++ (AFAIK) does not have the notion of a "logical
object".
There's only objects and their state. If (objects of) classes have
invariants, *and if* we agree that invariants have to hold for any valid
instance of a class, *and if* we agree that a moved-from object is a
valid instance of a class, then we should agree that invariants also
need to hold for moved-from objects.
Note that IMHO this boils down to the discussion (that was already done,
AFAIK), whether moving-from an object needs to preserve the class
invariants.
> (...)
>
> It actually gets better, or rather worse, as Michal Mocny commented
> this same problem will happen even if the Y object has a explicit user
> defined move constructor.
I think it does not. If you write a user-defined move-ctor that does not
preserve the class invariants, then it's buggy.
Dave states in a comment:
[http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/comment-page-1/#comme
nt-1326]
(...) If you code it by hand, you do
so in such a way that it doesn't
break the class invariants, of
course! (...)
> (...) And just imagine the invariant checking done in the
> destructor of InvariantChecker is 'a == "some value that I expect this
> string object to always have"'. Well at InvariantChecker destruction
> time 'a' is an empty string, so invariant validation fails. This all
> seems quite worrying to me, I hop I have just miss understood
> something. I would be interested in what you guys make of this all.
>
Yes but the value of a string is not a class invariant. I *may* be an
invariant of the local code, which held up to now because there was no
such thing as a move.
But as Dave states in another comment:
[http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/comment-page-1/#comme
nt-13379]
(...) Well, I think we may have
an issue here. It seems like the
fundamental problem is that the
move should happen at the point
of the returned object's destruction,
rather than at the return statement.
So maybe the move+return rules need to be adjusted.
> (...) The wrapped
> object may have moved elsewhere by the time the wrapper is destructed,
> only leaving some default state replacement in its place. I guess one
> can no longer assume one know the order that stack objects are
> destructed, as some might have moved beyond the closing "}" only
> leaving that very different default state object behind. Things sure
> seem a bit more complicated now with move semantics."
>
It is my opinion that moved-from objects are still valid objects. If
they are not, then the standard has to require the their dtor is called
on them.
The "default state replacement" has to be a valid state for the object.
cheers,
Martin
I can think of one example... when we intentionally want to move
(not sure if this is a real-world example, though):
Y move_out() {
std::unique_lock<std::mutex> l(m_mutex);
return std::move(m_y);
}
// if m_mutex unlocks before the actual move, there would be
a race condition.
It also makes return kind of special. What if there was:
void f(Y & res, bool choose) {
Y a, b;
InvariantChecker xa(a), xb(b);
res = choose ? a : b;
}
IMHO, this one should behave in the same way as return,
but under your proposed sequence, they do not.
--
Dragan
I don't understand what the problem is.
As I see it, if InvariantChecker is checking Y's invariant and that
invariant no longer holds then Y is broken.
OTOH if InvariantChecker is checking so see if Y is in some state it
thinks Y ought maintain then InvariantChecker's broken.
But I'm an innocent non-expert, so can someone please give me a clue
or point me at a good (easy) example?
Louis.
>From my point of view, the real argument is that the following
three pieces of code are not equivalent (as they were/are in C++03):
void f1(X & res) {
X x = blabla...
Y y(x);
res = x;
// y dtor sees the original value
}
X f2() {
X x = blabla...
Y y(x);
return x;
// y dtor sees the original value due to RVO
}
X f3() {
X x = blabla...
Y y(x);
if (something_silly) return error_x;
return x;
// y dtor sees the moved-out value due to move
}
... the destructor of y will "see" a different x, even if x's
invariants are preserved by its explicit or implicit move-ctor.
Personally, I don't really care about borderline cases that
get an implicit move constructor that destroys the invariants.
These classes can be fixed, and I'm not currently maintaining
any code with such borderline cases (therefore "personally").
However, the above samples are something that I will have
to integrate into my existing way of thinking, and it *does*
represent a substantial change, of much greater importance
than getting a faulty implicit move constructor
(under current rules, all my classes would do just fine).
Yes, I recognize that some of the above samples are different
as one constructs a return value, and the other assigns,
but in C++03, and by looking logically at the code,
they should be equivalent... Especially f2 and f3 should
not behave differently.
"y" may be some encapsulated RAII operating on "x",
it need not be invariance checker. Maybe I'll think of
a real example... I guess there were past discussions
about this, I just didn't follow them :-(
--
Dragan
Yes.
> OTOH if InvariantChecker is checking so see if Y is in some state it
> thinks Y ought maintain then InvariantChecker's broken.
>
Yes.
> But I'm an innocent non-expert, so can someone please give me a clue
> or point me at a good (easy) example?
>
The point is that Y (or the InvariantChecker -- or the f function, your
choice, really) got broken *just by upgrading to a new compiler that
generated a move-ctor for Y and then used the move-ctor in the f function.
No code was changed. But implicit move would mean that the code breaks
-- at runtime!
cheers,
Martin
Im no expert either. The implict move that happens when returning by
value from a function is an optimization. Its not supposed to change
the meaning of code (the as-if rule). Worse than breaking existing
code would be that just about every c++ would have to learn to
recognize this subtle gotcha. If the move constructor was also
implictlly generated, there would be no visual hints at all in the
code that the problem had anything to do with an implicit move return
(Please note though that the impicit generation of move constructor is
not the problem here, its the implicit move return. The implcit move
return would also happen if the object had an explicit move
constrcutor).
That said.. I think this problem is solved now. If the compiler delays
the move of an returned local object, until just before the
destruction of that local object, then everything should be fine. That
makes a lot of sense to me, if one considers that move really is half
a destructor (as it tears down some invariants). You want to do
destruction as one unit. You really dont want the returning by implcit
move to cause half a destruction to happen, any earlier than a full
destruction would have happened had this been a return by copy.
Thanks
The problem is of course that the invariant is NOT broken under the
current rules, which copies the return value and leaves the invariant
unchanged. Adding a default move constructor to Y breaks existing
code. A Bad Idea(tm).
Bo Persson
After think about it some more I realize you are right, on both
counts. The "fix" would make things worse.
<code>
Val func() {
Val lv(666);
Mutex lm; //wants to scope out after move construction of
destination is completed
InvariantChecker lic(lv); //wants to scope out before move
"destruction" of source is begun
//do stuff
return lv;
}
</code>
I see now that there is no having the cake and eating it. It seems to
me the problem is that move return is really doing two things. It
destroys some of the source, and constructs all of the destination.
While copy return just does one thing, constructs destination. So
there will always be some possibility of the semantic difference
breaking things. The ultra conservative fix would be to require
programmers to be explicit about move return as well. Maybe that would
not be such a bad idea, as differences that matter should not be
implicit but fully visible in code. I guess the question then becomes
if it matters enough?
Thanks
--
That is a good point. So if I understand your examples correctly. The
(implicit) RVO optimization has priority over the implicit move return
optimization. And because in the f3 example RVO is not possible
because of the multiple return paths, it falls back on implicit move
return. And thereby changing some semantics. Yes, that's might also be
an issue.
I guess one way to make them have the same semantics is for implicit
move return to have priority over (implicit) RVO. That way it does not
matter whether there is one or more return paths. I guess the down
side to that is that move return is slower than RVO. The other option
is again to disable implicit move. As the (default) copy and
(implicit) RVO has the same semantics. The thirs option is not to
change anything, but then we get these subtle gotchas.
Between your example and Daves. I'm beginning to think disabling
implicit move return is the best option. Better if the default is
correct code instead of fast code. Its not so much extra work to type
"return move(val);" instead of "return val;". Even beginners can learn
to do that.
And then you would not have to be an expert to know if the return is a
move or a copy. Beginners want to know this because copy is expensive
and move is cheap. So there should be an easy way of finding out. The
way things are now, to find out if the return is a move or a copy you
have to go look in the definition of the type, and if there are no
move constructors there, you have to go looking in the standard for
the rules when implicit move constructors are generated. I think
rather than make things easier for beginners to understand, implicit
move makes things harder.
Thanks.
--
But! Note that if Y is a std::type, and if the InvariantChecker is a
PostConditionChecker which asserts on some (local) value of the std::type, then
the code will break even in the absence of implicitly generated move-ctor, since
the std::type will have an explicit move-ctor after compiler upgrade.
So with the current automatic-move-at-return rules, some code could always
break. Therefore the question is, if completely removing implicit move-ctor
generation is the right fix.
cheers,
Martin
Agreed.
> The ultra conservative fix would be to require
> programmers to be explicit about move return as well. Maybe that would
> not be such a bad idea, as differences that matter should not be
> implicit but fully visible in code. I guess the question then becomes
> if it matters enough?
Requiring an explicit std::move(lv) means that the author of func()
has been forced into an uncomfortable choice:
1. Don't move and hope RVO does the job.
2. move and know that RVO has now been inhibited.
3. Generic factory functions must use move (and inhibit RVO) lest
their returned type not be CopyConstructible.
The move-on-return rule was first proposed 8 years ago. It was put
into the draft standard over 5 years ago. It has now been widely
implemented and used.
* It provides a /very/ high benefit.
* I am not aware of any actual (in the wild) code that has been
adversely impacted. I'm sure there will be some eventually. But
indications are that such code is extremely rare.
* If there is an adverse impact, the fix is cheap and easy:
<code>
Y f(bool choose)
{
Y a, b;
{
InvariantChecker xa(a), xb(b);
// ...
} // ~InvariantChecker() run here
if (choose)
return a;
return b;
}
</code>
Which language design results in the largest benefit to the C++
community as a whole? What language would you like to program in?
-Howard
I agree with every single point. Implicit move return should only
happen when returning a rvalue, either by returning a temporary,
or by explicit std::move(). That way semantics are correct
and well defined and do not change because of an "optimization".
(Unless another approach comes that makes implicit move return
behave identically as RVO)
But I don't see anyone else supporting this! To me, the difference
between f2() and f3() will make people come up with even more
rules of thumb about what to do and what not to do when
an object is movable (and when writing a generic code,
then we'll take the worst case scenario). This should not happen!
The code should not have hidden magic powers! It should express
our logic cleanly...
Hell, if we fear that implicit move return would override
a potential RVO, then add a RVO explicit language functionality
(without the need to abandon the implicit RVO optimization).
R func() {
R res return;
calculate(res);
if (something_silly) return global_r; // ERROR, must return "res"!
return res;
}
or copy the syntax of an earlier GCC extension.
Hope my input helps...
--
Dragan
I think to a certain extent it is nonsense.
The class invariants should not be applied to a moved from object
(whether the move constructor is compiler-generated or not).
A moved from object (after a move constructor has done its work) is
roughly equivalent to a house that is gutted, no floors, where the
only thing that can be done is destroy it (i.e. apply the destructor).
The object is non-comparable to a newly created object (something like
a fully furnished house with floors) where a constructor has just run
or where object state-transforming member functions have been applied
where I would expect class invariants to hold.
To rescue the concept of class invariants, you either
(i) allow the concept of class invariants to include the gutted state
(probably not useful)
(ii) or you say it is no longer valid to apply a class invariant to a
moved from object.
I assume that the thread conversation is holding as axiomatic that
whatever the gutted state of a moved from object, the destructor can
be safely applied.
If that is non-guranteed, then I think that the addition of move
constructors to C++0x is non-viable.
Stephen Howe
Perhaps you use a wrong definition of class invariants. If you allow
the destructor to run in such a "gutted" state, then class invariants
implicitly include such "gutted" state. Unless we are to start writing
destructors that shall be overly pessimistic and assume that the object
can be in any weird state not assumed by any other member function.
I don't like such a destructor concept.
The basic idea of move operation, including that it leaves the object
in a well defined state (where invariants still apply), is quite sound.
From my point of view, it has been defined completely
in the spirit of C++.
But what about its realization?!?
One of the things I don't like about moving is that "x = std::move(y)"
will COPY if the class has no move assignment operator and it will
do this silently without diagnostics. This is logical only to us
that have gotten used to the concept over the years, but it is
not logical in a broad sense.
IMHO, many details related to rvalue reference and move give
impression of being chosen in order to solve only few problems
(perfect forward, optimization on return)... and then
are trimmed in order not to cause any other problems.
This method should not apply to anything that is logical
and sound (only to a quick-fix or a workaround... or state laws)!
It is a bit embarrassing to criticize when my contribution
to the issue was insignificant... for this, I apologize
to people that did the hard work.
--
Dragan
Wow. That is surprising to me, I would have expected an error. This
thread has more twists than a Night Shyamalan movie. Looking it up ...
So if you call std::move with a const lvalue it casts it to a const
rvalue, which cant be moved so it is silently copied instead of moved.
Why is that behavior wanted? Is it for generic algorithms? If it is
useful, then at least std::move has a misleading name (std::anon,
std::move_or_copy would have been closer to its behavior). I predict
many programmers will want a move function that guarantees a move and
fails for const lvalues, and they will write such a function
themselves and use it instead of std::move. That will be an even
messier situation than if there was two versions of move in the
standard.
I'm starting to see a trend here. Lots of choices about move, are
being made automatically for the programmer. With the implicit move
constructor generation, implicit move return, and "implicit copy on
failure" of std::move. I guess hiding choices makes
things look simpler. But it also makes things more confusing if you
want the other option. What happened to trusting the programmer?
I know its probably too late for these discussions to make a
difference, but there sure are a lot of gotchas here to discuss.
Thanks, Patrik
--
Not only for const lvalue... It will also silently copy a non-const
lvalue object that has no explicit or implicit move constructor.
However, it will fail with an error if we explicitly delete
the move constructor. Strange, isn't it.
The problem is that current language rules do not support such
move function. If there is no move constructor/move assignment operator,
then copy constructor and move assignment operator will pick up
those rvalue arguments. Maybe you can force it to fail with
some special template/SFINAE/whatever kung fu, but it is not
supported. Someone correct me if I'm wrong.
This is why I consider it a workaround... it has been trimmed
and tuned in order to give the most benefit and the least
troubles. Will I learn to live and work with it and think in terms
of it? Most likely yes... It may even become natural to me.
I've already tried to play around with it; it all works for
most basic usages. But I feel quite uneasy...
Hm... I foresee there will soon be a quick fix that will make
classes that have no explicit or implicit move constructor,
instead of getting no move constructor at all, get one that
is implicitly deleted. How about that? :-D
Many of my posts where I question some functionality end up
ignored, or are answered by only one person (such as now :-)).
I have no clue whether my words are plain nonsense and just
give up and forget it. I would gladly turn something
into a proposal or whatever is appropriate if there would be
some support and maybe help from the community. But so far,
I consider that I speak nonsense, or that my English is poor.
Anyway, it's too late now, we have to "move" on
(unless we get copied/cloned instead)...
--
Dragan
> Perhaps you use a wrong definition of class invariants. If you allow
> the destructor to run in such a "gutted" state, then class invariants
> implicitly include such "gutted" state.
I allowed for that possibility in (i). I just dont think it is useful.
If you look at the OP's link and follow the links, you arrive at
Scott Meyers example code in
http://cpp-next.com/archive/2010/10/implicit-move-must-go/
All that matters from the destructors point of view is that the object
is in a state where resources can be reclaimed, there is no undefined
behaviour. It is utterly irrelevant from the destructors point of view
that the full class invariants still hold true when execution enters
the destructors body.
In Scott Meyers example, the comment was
// invariant: v.size() == 5
I am saying that need not be true after a move constructor has been
run, all that matters is the object is in a sufficiently coherent
state such that the destructor can run.
v.size() could be 0 and the destructor would not be bothered.
So I guess I am saying that there should 2 class invariants, a strong
one and a weak one.
And the strong class invariant holds after the constructor has run and
for all mutating public (or protected) operations on the object. But
if the object has the move constructor applied to it, from then
onwards, only the weak class invariant holds until the object is
destroyed.
> The basic idea of move operation, including that it leaves the object
> in a well defined state (where invariants still apply), is quite sound.
I mostly agree, but by "well defined state", I would say that the
object is in a state where if the destructor runs, that is fine, there
is no undefined behaviour on completion.
Cheers
Stephen Howe
--
I am surprised this surprises you, since you initiated the thread
about rvalue references.
rvalue references allow you to distinguish lvalues and rvalues, which
can be used to implement safe and automatic move semantics. They're
not exactly magic either.
> So if you call std::move with a const lvalue it casts it to a const
> rvalue, which cant be moved so it is silently copied instead of moved.
What std::move does is casting to an rvalue, indeed.
> Why is that behavior wanted? Is it for generic algorithms?
Yes, Copyable is a refinement of Movable, i.e. any copyable object is
also movable, as a copy is seen as a special case of a move.
> If it is
> useful, then at least std::move has a misleading name (std::anon,
> std::move_or_copy would have been closer to its behavior). I predict
> many programmers will want a move function that guarantees a move and
> fails for const lvalues, and they will write such a function
> themselves and use it instead of std::move. That will be an even
> messier situation than if there was two versions of move in the
> standard.
This doesn't make sense from the algorithm point of view. The
algorithm doesn't know what copying and moving means for the object.
If you want to make sure you never copy, just make your object non
copyable. It is the object that can detect whether it gets passed
lvalues and rvalues, and which may act accordingly to copy or move.
Also it's not really possible to write a move function that generates
an error if a copy ends up being invoked.
> I'm starting to see a trend here. Lots of choices about move, are
> being made automatically for the programmer. With the implicit move
> constructor generation, implicit move return, and "implicit copy on
> failure" of std::move.
Implicit conversion to rvalue and implicit move constructor generation
are two entirely different things.
> I know its probably too late for these discussions to make a
> difference, but there sure are a lot of gotchas here to discuss.
There aren't a lot of gotchas, just the single issue of automatic move
constructor generation.
And the problem here is compatibility with legacy code that didn't
follow the rule of three (if you need one of destructor, copy
constructor or assignment operator, then you need all three).
The part that concerns me the most is this comment from Abrahams:
"There are any number of obvious ways to make the syntax ... more
palatable, but it's much too late in the standardization process to
invent any new language features to handle this problem."
I really don't want this to be the case. I already have a hard enough
of a time advocating C++ to people without having to add another
chapter to the voodoo rules of C++.
[1] http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/
I thought that the issue[1] is specifically *not* about code that has
anything to do with the rule-of-three. The code David shows in his post
is just using a normal ctor and doesn't need either of the Three.
The problem is that an (implicitly generated) move-ctor is changing its
moved-from argument in a way that wasn't possible before C++0x and
legacy code can break in specific cases where the moved-from object is
still referenced. The example was an invariant checker.
cheers,
Martin
[1] http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/
Destroyed or assigned to.
Thanks for your reply. It cleared one miss-understanding of mine. As
up until now we have only been discussed implict move on return, as no
example actually used explicit std::move. So I had managed to stay
ignorant of the behaviour of explicit std::move until now. My (naive)
expectation was that explicit std::move meant move semantics _should_
be used, not merely that move semantics _could_ be used. The main
issue discussed has been that we have no way of requesting copy
semantics for returned objects that have both copy and move
constructors (whether they be implicit or explicit). So while I
imagined a fix for this would be to disable implicit move return, and
require the user to explicitly tell the compiler to use the move
constructor. If I understand correctly what the explicit std::move
tells the compiler is that both move and copy semantics are Okay, and
because the move constructor has priority over copy constructor the
compiler will choose the move constructor if possible (if the object
was non-const, and had move constructor). So different, but same end
result.
> The algorithm doesn't know what copying and moving means for the object.
> If you want to make sure you never copy, just make your object non
> copyable. It is the object that can detect whether it gets passed
> lvalues and rvalues, and which may act accordingly to copy or move.
To me that seems like a one size fits all strategy. In some contexts
the different semantics between move and copy does matter, in most
contexts it does not. We have no way to tell the compiler when it
matters. Like in the InvariantChecker example. It's also intrusive,
what if we cant change the definition of the object but still want to
keep the old copy semantics.
> Also it's not really possible to write a move function that generates
> an error if a copy ends up being invoked.
My bad. From my naive point of view, such a function would be very
useful. The sort of thing a generic algorithms would like to know
whether std::move is cheap or as expensive as a copy. I guess its
impossible to tell because rvalue references bind to const lvalue
references. So a rvalue refernce will bind to the const reference
argument of copy constructor.
> Implicit conversion to rvalue and implicit move constructor generation
> are two entirely different things.
I realize this, sorry about the misleading title.
> There aren't a lot of gotchas, just the single issue of automatic move
> constructor generation.
There are at least two subtle gotchas with return. Its not easy to
tell if a function returns by moves, copy, or RVO's. There is a subtle
semantic difference between move return and copy/RVO return, that
sometimes breaks code (InvariantChecker example). And maybe worse
there is a subtle trigger that changes RVO semantics into move
semantics (f3 example). Those are two very subtle gotchas, that have
nothing to do with implicit move constructor generation. Then there is
the gotcha that std::move does not ensure a move happens.
Thanks, Patrik
Nobody really wants that, if it we have the choice. What Dave Abrahams
means is that the next release of the standard is already considered
feature complete, in a state of "beta release". Bug fixes are
accepted and processed, but no new features. The committee has even
removed previously accepted features, just to speed up the next
release.
Bo Persson
> The part that concerns me the most is this comment from Abrahams:
>
> "There are any number of obvious ways to make the syntax ... more
> palatable, but it's much too late in the standardization process to
> invent any new language features to handle this problem."
>
> I really don't want this to be the case. I already have a hard enough
> of a time advocating C++ to people without having to add another
> chapter to the voodoo rules of C++.
>
> [1]http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/
The ironic thing is that the link you site has not yet offered a code
example that gets broken.
-Howard
Yes, for someone who is new to rvalues I find all of this surprising.
I created this example to see If I understand you.
<Code>
struct A {
A() {}
~A() {}
//private:
// A(A&& a);
};
main() {
A a0;
A a1(static_cast<A&&>(a0));
}
</Code>
So this compiles without error since there is no implicit move
constructor (because of the explicit destructor) but there is an
implicit copy constructor. And A&& binds to the const A& copy
constructor. I don't have a compiler that supports the =delete syntax
so I cant try that, but if you uncomment the private declaration of
the move constructor the example fails with an error. If that's how
the =delete version of the example also works then I do find it
surprising. You'd think that a class with no move constructor would
behave the same as one where it has explicitly been deleted.
The more I learn about move semantics the less I know. I guess a
programmer is not supposed to ask whether something is a copy or a
move, even though that seems like an important question to me. All
this implicitness makes me feel uneasy as well. I'd prefer if
something is a move or copy was visible clear as day in the code. No
implicit move constructor generation, no implicit move return, no
binding of rvalue references to const references (I think, to avoid
possibility of copy construction when assigning from std::move).
Thanks, Patrik
--
It's not references that bind to references, but rvalue binds to
a const reference (or was it the other way around). This is
how it goes now, and there is no changing that...
Foo func_1();
void func_2(const Foo & foo);
func_1(func_2());
--
Dragan
Follow the links Howard.
You arrive at Scott Meyers example:
http://cpp-next.com/archive/2010/10/implicit-move-must-go/
which does break in the destructor as v[0] is undefined.
Stephen Howe
No, a class without a move constructor behaves like a C++03 class.
Your cast presents an rvalue to the copy constructor of the class,
which is no different from what it usually gets from a function return
value.
On the other hand, if you *do* have a deleted move constructor, that
is a better match than the copy constructor. But as you have deleted
it, it cannot be used.
>
> The more I learn about move semantics the less I know. I guess a
> programmer is not supposed to ask whether something is a copy or a
> move, even though that seems like an important question to me. All
> this implicitness makes me feel uneasy as well. I'd prefer if
> something is a move or copy was visible clear as day in the code. No
> implicit move constructor generation, no implicit move return, no
> binding of rvalue references to const references (I think, to avoid
> possibility of copy construction when assigning from std::move).
>
If a class is moved or copied is defined by the class, not by the code
using the class. Is that part of the confusion? :-)
Using std::move you say that it is ok to move the object (because you
don't care about it anymore), not that it absolutely must be moved.
Copying is a way of moving, where the original is left intact. If you
move an int, it will keep its value. So might some class objects.
Bo Persson
In another reply to this message, I have thanked you for explaining
these concepts. And then I thought about it some more:
struct Foo {
Foo(const Foo &);
Foo & operator=(const Foo &);
};
Foo is Copyable. Therefore, it is also Movable.
struct Bar {
Bar(const Foo &);
Bar(Bar &&) = delete;
Bar & operator=(const Bar &);
Bar & operator=(Bar &&) = delete;
};
Is Bar Copyable? I would say so... But is it Movable? It must
be since it is Copyable. But "bar_2 = std::move(bar_1)" will fail
with an error.
Could you clear things up concerning this? I would appreciate
it very much...
--
Dragan
Many thanks for clearing up the concepts of Movable and Copyable
and their relations to each other and to algorithms that use them.
I understand what you meant, I will just give it some time
and thought to see if they "grow on me" :-)
Please correct me if I'm wrong...
I understand that whether "y = std::move(x)" copies or moves should
not matter from algorithmic point of view because after this
one the algorithm MUST NOT use "x" anymore. Even if "x" was
unchanged and got copied instead of moved, the algorithm
must not use it.
But I still think that return by move optimization is bad.
"return x" has always meant "return a copy of x", with additional
liberty of "as-if" rule that allows copy elision. If the meaning is
to be changed to "return by moving (where copy is a special case
of moving)", then IMHO that is not merely an optimization, but
a significant change that needs to be written in large letters
on the package of every C++0x compiler!
(Additionally, as I made a mess with my posts, there is another
reply that raises a question regarding the concepts you
have explained.)
Then I think that you have not grasped the purpose of =delete. One of
the major motivations was to replace the hack of declaring a function as
private and then not defining it.
The idea is that the deleted function has been declared and fully
participates in overload resolution. If it is selected the
implementation will issue a diagnostic.
This is a very powerful/useful addition to the language. For example we
can now suppress unwanted conversions (and not just at class scope)
int foo(short);
int foo(int) = delete;
The result is that it becomes a diagnosable error to call foo() with an int.
It is also important that the =delete syntax makes the behaviour
explicit at the point of declaration and so improves the ability of code
to document itself.
--
ACCU Conference 2011 The one you cannot afford to miss
Unless I am mistaken, I haven't seen how the Abraham's code in the
original post isn't broken. (This example is listed in the comments).
The InvariantChecker that checks the state of another object in its
destructor will fail because of an implicit move because it silently
working on an object that is no longer valid.
This code would have worked under the existing standard, and will
break under the new one.
What's even stranger is whether the invariant will or won't pass also
depends on whether or not someone adds or removes a user-defined
destructor somewhere else.
Why would you say that?
The discussion here and in at the cpp-next site clearly highlights two
(2) cases where (hypothetical) existing code breaks either through
implicitly generated move-ctor or through implicit move-at-return.
(Note that I'm not saying that either is necessarily a show stopper or
that the current rules need to be fixed, but breakage it is.)
I agree that we haven't yet seen an example from real production code
that would be affected.
cheers,
Martin
This link was written prior to the 2010 Fall standards meeting in
Batavia which fixed the problem in this link.
This thread is no longer about the implicit generation of move
constructors. It is about implicitly considering the expression of a
return statement when that expression is eligible for RVO.
There is *way* too much confusion and mis-information in this thread.
I *strongly* encourage all participants to post code, to post the
results from having actually compiled/tested that code, and to post
what compiler was used to obtain those results.
I stand by my statement:
http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/
has not posted code that actually breaks.
I do not claim that such code doesn't exist. I only wish to have this
discussion based in fact, not in fud.
-Howard
Well, there *is* an interesting example in one of the comments:
Y f(bool choose)
{
Y a, b;
InvariantChecker xa(a), xb(b);
// The use of ?: guarantees that there will be no copy elision.
// Instead, either a or b will be *moved* into the return value.
return choose ? a : b;
// xb and xa get destroyed here
// b and a get destroyed thereafter
}
But I'm currently not sure whether the C++ draft allows/forces a
compiler to move construct the return value in case of a return
expression like choose?a:b. But if you change this to
if (choose) return a;
return b;
it's easy to see that either a or b will be "moved into the return
value". This code will fail if the compiler implicitly generates a
move ctor for Y and the dtors of xa and xb perform some final
operations on a and b that require a and b not to be in a "moved-from"
state. So, in other words, this problem arises because the following
three things come together:
(1) implicitly generated move ctor for Y
(2) (unanticipated) implicit move from a or b
(3) a or b are about to be destroyed BUT they are accessed
AFTER the return expression (in time) due to some other
objects' dtors (xa/xb.~InvariantChecker).
So, yes, there are examples that work in C++03 and break in C++0x at
runtime which is unfortunate. But there are not a lot of options.
These are the ons I currently see
(A) Keep the current rules and live with them
(B) disable implicit generation of move ctors
(C) disable/tighten the rules about "implicit move returns".
*NOT* an option
- delaying move construction (hard to implement and/or might
break other code, see lock_guard example in the comments)
I'm leaning towards A because I think that these breaking examples are
rather artificial and hardly to be found in real code.
Cheers!
SG
If by "Copyable", you mean: meets the requirements of
CopyConstructible and CopyAssignable, then no, Bar is not Copyable.
See [utility.arg.requirements], Table 37 -- CopyConstructible
requirements, and Table 39 -- CopyAssignable requirements.
-Howard
--
Bar does neither satisfy the requirements for
MoveConstructible/Assignable nor does it satisfy the requirements for
CopyConstructible/Assignable.
This is so, because CopyConstructible/Assignable are refinements
of the requirement sets MoveConstructible/Assignable, but the latter
are not met for type Bar, because the initialization
Bar u = rv;
and the expression
t = rv
with an rvalue rv of type Bar an an lvalue of non-const Bar are not
supported because of the deleted move functions. In fact, this
requirement (initialization/assignment from an rvalue) did already exist
in C++03 and keeps valid in C++0x.
HTH & Greetings from Bremen,
Daniel Krügler
--
Copy elision is not covered by the as-if rule. It changes the
observable behaviour and is explicitly allowed. Moreover, if one
disallowed the compiler to do an implicit "move return" people would
have to write return std::move(...) to take advantage of move
semantics where copy elision is NOT applicable anymore. That would be
unfortunate, because RVO is even bettern than a "move return".
Here's Dave's code, completed and instrumented:
#include <iostream>
class Y
{
int state_;
public:
enum {destructed = -2, moved_from, default_constructed};
Y() : state_(default_constructed) {}
Y(const Y& a) : state_(a.state_) {}
Y& operator=(const Y& a) {state_ = a.state_; return *this;}
Y(Y&& a) : state_(a.state_)
{
a.state_ = moved_from;
}
Y& operator=(Y&& a)
{
state_ = a.state_;
a.state_ = moved_from;
return *this;
}
~Y()
{
state_ = destructed;
}
explicit Y(int s) : state_(s) {}
friend
std::ostream&
operator<<(std::ostream& os, const Y& a)
{
switch (a.state_)
{
case Y::destructed:
os << "Y is destructed\n";
break;
case Y::moved_from:
os << "Y is moved from\n";
break;
case Y::default_constructed:
os << "Y is default constructed\n";
break;
default:
os << "Y = " << a.state_ << '\n';
break;
}
return os;
}
friend bool operator==(const Y& x, const Y& y)
{return x.state_ == y.state_;}
friend bool operator<(const Y& x, const Y& y)
{return x.state_ < y.state_;}
};
class InvariantChecker
{
Y* v_;
InvariantChecker(const InvariantChecker&);
InvariantChecker& operator=(const InvariantChecker&);
public:
explicit InvariantChecker() : v_(0) {}
explicit InvariantChecker(Y& v) : v_(&v) {}
~InvariantChecker()
{
if (v_)
std::cout << *v_ << '\n';
}
};
Y
f(bool choose)
{
Y a(1), b(2);
InvariantChecker xa(a), xb(b);
// Incorrect comment follows:
// The use of ?: guarantees that there will be no copy elision.
// Instead, either a or b will be *moved* into the return value.
return choose ? a : b;
// xb and xa get destroyed here
// b and a get destroyed thereafter
}
int main()
{
Y y = f(true);
}
Using a recent build of clang and libc++ this, at least for me
outputs:
Y = 2
Y = 1
Using g++-4.4 with libc++ I get the identical output. To demonstrate
Dave's point I expect to see:
Y is moved from
in the output. If someone copy/pastes this code into another
environment and gets different results, please post that.
Again (emphasis), I'm not claiming that Dave's code can't be made to
demonstrate Dave's complaint. I just think it would be a good idea if
we discussed (and tested) actual code.
-Howard
Maybe you realize this. But please not that that this example would
break, both with implicitly generated move constructor, and with well
defined explicit move constructors. So the only options for this
example are A) or C). I would accept A) and start considering
InvariantChecker a design flaw if someone came up with an easy rule of
thumb for it. Ideally something as straight forward as the "never
throw an exception form a destructor" rule. All I can come up with is
"In a destructor assume any alias to objects you don't own have been
implicitly moved from, don't depend on their state." Thats still a bit
subtle but it's what I came up with. Anyone got better ideas?
Thanks, Patrik
This sure has been an interesting thread. I have learned and
unlearned, thanks everyone. So to see if I understand your position.
Let me write it out. Please correct me if I miss-represent.
The programmer does not control whether a move or copy happens. The
programmer can only enable or disable move semantics for an object.
Only the compiler is free to choose when the more general move
semantics will be chosen instead of the specific copy semantics. The
programmer has no way to force the compilers hand. With std::move you
are merely telling the compiler that your code only asumes the general
case move semantics. The compiler takes this as a hint, but you wont
know if the compiler ends up doing a copy or a move (without infering
this from the rules the compiler uses to make its decisions). So the
policy is "dont ask and dont tell". Because copy semantics is a
subtype of the more general move semantics, there should not exist any
object for which copy semantics make sense but for which move
semantics wont make sense (given some well defined copy constructor,
who's body is not merely a replica of the copy constructor).
That clears up most of my confusion, as there is some "method to the
madness". To calm my unease though, I would like to have some kind of
guidline that identifies why InvariantChecker like objects breaks, and
why they deserve to break. So 1) the InvariantChecker object is using
an alias to another object. Nothing bad there. 2) The InvariantChecker
object depends on another object's state (not merely the other objects
internal invariants). Nothing bad there either 3) but when the
InvariantChecker object does 1 and 2 in its destructor, it might break
(depending on context InvariantChecker is used in, which we have no
control over). We now have silently introduced the unacceptable
additional requirement that the referenced object has to have had its
move semantics disabled. That we did this is not obvious just by
looking at the class defintion of InvariantChecker. If we cant easily
spot that InvariantChecker has this serious design flaw, how can hope
to avoid writing such code? A good guidline would not depend on the
context of how InvariantChecker is used, as that is outside of
InvariantChecker's control. Algorithm developer can only give the
compiler hints when move semantics is acceptable (by using std::move),
not when move semantics is unacceptable (say by using some magic
"return std::ccopy(val);").
Maybe someone can come up with such a guideline because I cant come up
with it.
Thanks, Patrik
> Here's Dave's code, completed and instrumented:
>
>
> #include <iostream>
>
> class Y
> {
> int state_;
> public:
> enum {destructed = -2, moved_from, default_constructed};
>
> Y() : state_(default_constructed) {}
> Y(const Y& a) : state_(a.state_) {}
> Y& operator=(const Y& a) {state_ = a.state_; return *this;}
> Y(Y&& a) : state_(a.state_)
> {
std::cout << "Moving out!\n";
Using the -fno-elide-constructors flag in clang or gcc shows that the
move occurs after the InvariantChecker destructors.
Naturally, it helps a lot...
Before Mathias Gaunard, Howard Hinnant and yourself explained
the logic and concepts behind these, I only had the understanding
about the effects of the code (Will it bind to this reference?
How do overloads work? How does "= delete" work? How to call
a move assignment operator? How to write a move constructor).
Now things make sense and I have a different view.
So, my main enlightenment here is that overloaded functions
MUST be observed together as one function in order to actually
get the meaning of the declaration. This is a bit different than
a basic (can it be called "basic"? or should it be called "bad"?)
example where you use one function name to cover different sets
of arguments or different domains.
So, X(const X &) and X(X &&) are actually one "entity".
I regret that it wasn't possible to make these two declarations
into one declaration, even if it had two different implementations.
(Perhaps it could be an extension for C++1x?)
This would make it more comprehensible and more clean...
The way things are now, one of the two overloads (and I don't
mean just ctor/assignment overloads, but also other functions
that overload "const Arg &" and "Arg &&"), may be declared
somewhere else in the code, and completely ruin the understanding
of the code.
It also makes it hard to explain to someone who isn't already
an expert in C++(03) about these overloads and the relation
between them. It just isn't natural outside of the C++ world.
Did I get it right this time?
BTW, I still have issues with "implicit move return". :-(
Thanks
--
Dragan
No, it did *not* fix it.
> This thread is no longer about the implicit generation of move
> constructors. It is about implicitly considering the expression of a
> return statement when that expression is eligible for RVO.
>
This thread here - maybe. But not the post by David Abrahams.
> There is *way* too much confusion and mis-information in this thread.
I agree about the confusion.
> I *strongly* encourage all participants to post code, to post the
> results from having actually compiled/tested that code, and to post
> what compiler was used to obtain those results.
>
Which compiler (version) that I could easily use on Windows already
implements the lates std draft?
> I stand by my statement:
>
> http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/
>
> has not posted code that actually breaks.
>
> I do not claim that such code doesn't exist. I only wish to have this
> discussion based in fact, not in fud.
>
http://cpp-next.com/archive/2010/10/implicit-move-must-go/
lists the example tweak#2 - class has no ctor but still a class
invariant. (I'll re-post the code at the end.)
Per the new rules
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3203.htm
If the definition of a class X does not explicitly
declare a move constructor, one will be implicitly
declared as defaulted if and only if
* X does not have a user-declared copy constructor,
* X does not have a user-declared copy assignment operator,
* X does not have a user-declared move assignment operator,
* X does not have a user-declared destructor, and
* the move constructor would not be implicitly defined as deleted.
this example *still* breaks.
Note again: I'm not saying implicit-move-ctors should necessarily be
removed completely, I'm just saying that Dave *does* have a point.
cheers,
Martin
- - -
Code Example Tweak#2 from David's post @
http://cpp-next.com/archive/2010/10/implicit-move-must-go/
- - -
#define _GLIBCXX_DEBUG
#include <iostream>
#include <vector>
// An always-initialized wrapper for unsigned int
struct Number
{
Number(unsigned x = 0) : value(x) {}
operator unsigned() const { return value; }
private:
unsigned value;
};
struct Y
{
// Invariant: length == values.size(). Default ctor is fine.
// Maintains the invariant
void resize(unsigned n)
{
std::vector<int> s(n);
swap(s,values);
length = Number(n);
}
bool operator==(Y const& rhs) const
{
return this->values == rhs.values;
}
friend std::ostream& operator<<(std::ostream& s, Y const& a)
{
for (unsigned i = 0; i < a.length; ++i)
std::cout << a.values[i] << " ";
return s;
};
private:
std::vector<int> values;
Number length;
};
int main()
{
std::vector<Y> z(1, Y());
Y a;
a.resize(2);
z.push_back(a);
std::remove(z.begin(), z.end(), Y());
std::cout << z[1] << std::endl;
};
- - -
> Using the -fno-elide-constructors flag in clang or gcc shows that the
> move occurs after the InvariantChecker destructors.
You are observing the move construction from the temporary returned by
f to the local y in main. -fno-elide-constructors has no impact
because elision has been inhibited in the source code.
-Howard
Apparently, GCC won't move from a or b if you use this kind of return
expression. I'm not sure about what the standard draft says about this
case. But if you replace this line with
if (choose) return a; else return b;
you will see the following output
Y = 2
Y is moved from
(tested using GCC 4.5.1)
> // xb and xa get destroyed here
> // b and a get destroyed thereafter
> }
>
> int main()
> {
> Y y = f(true);
> }
>
> Using g++-4.4 with libc++ I get the identical output. To demonstrate
> Dave's point I expect to see:
>
> Y is moved from
Right. Change the above line and you will see this output.
On 23 Feb., 12:09, Dragan Milenkovic wrote:
>
> BTW, I still have issues with "implicit move return". :-(
What is your solution? :-)
Let me stress again that I don't think forcing users to write
return std::move(some_function_local_object);
instead of
return some_function_local_object;
is a good idea. Here's why:
- The use of std::move() DISABLES any kind of RVO but RVO would be
even better than a move construction of the return value.
- If RVO cannot be applied, a move construction is almost always
the right thing to do (except in corner cases like the above one).
I wouldn't want to be forced to write std::move all the time.
Cheers!
SG
I ran your code on VC10 and got the same result as you. However if I
change the conditional to:
> Y
> f(bool choose)
> {
> Y a(1), b(2);
> InvariantChecker xa(a), xb(b);
if(choose)
return a;
else
return b;
>
> }
Then I get the output I expected:
"Y = 2
Y is moved from"
Not sure why that makes a difference. Just goes to show how subtle
changes like this can change the semantics.
Thanks, Patrik
Replace with:
if(choose) return a;
return b;
> // xb and xa get destroyed here
> // b and a get destroyed thereafter
> }
>
> int main()
> {
> Y y = f(true);
> }
>
> Using a recent build of clang and libc++ this, at least for me
> outputs:
>
> Y = 2
>
> Y = 1
>
> Using g++-4.4 with libc++ I get the identical output. To demonstrate
> Dave's point I expect to see:
>
> Y is moved from
>
> in the output. If someone copy/pastes this code into another
> environment and gets different results, please post that.
With this small change (I have finally found that the implicit move on
return thing is right next to copy elision in the standard), it does
print "Y is moved from".
[...]
Your conclusion is incorrect, in C++0x you can have two completely separate and different special member functions, a copy constructor with the canonical signature X(const X &) and the move constructor with the canonical signature X(X &&). The point that you seem to miss is that there is a difference between above member functions and the library requirement set CopyConstructible and MoveConstructible. Requirement sets of the Standard Library are sets of expressions or initializations and a required semantics and/or post conditions.
For example, the MoveConstructible requirements impose that a type T that satisfies this requirement set shall support an initialization
T u = rv;
and expression
T(rv)
where rv is an rvalue of T. This is *no* requirement for an existing move-constructor! It just means that above expressions are well-formed and that they satisfy further requirements. E.g. the fact that the destination of this construction is equivalent to the source before
the construction had taken place. In fact, you can satisfy this requirement if you have a copy-constructor but no move constructor.
Here is an example of a type C which has a move-constructor but does *not* satisfy the MoveConstructible requirements:
class C {
int state;
public:
explicit C(int state) : state(state) {}
C(C&&) : state(42) {}
int get_state() const { return state; }
bool operator==(C rhs) const { return state == rhs.state; }
};
C(1) c1;
const int state1 = c1.get_state();
C(2) c2 = std::move(c1);
assert(c2.get_state() == state1); // Error!
> I regret that it wasn't possible to make these two declarations
> into one declaration, even if it had two different implementations.
> (Perhaps it could be an extension for C++1x?)
This won't happen: Note that the special member functions are completely
different from requirement sets.
> This would make it more comprehensible and more clean...
It does not. There is much of a difference between concrete functions and supported expressions. A single expression requirement can sometimes be realized by different functions.
HTH & Greetings from Bremen,
Daniel Krügler
The problem AFAICT in this example is that the implicit generated move
constructor for Y breaks the (implicit) invariant of Y. The invariant
is that "values.size() == length".
The only way I can see to fix this example is to extend the rules, so
no implicit move constructor gets generated for classes that have data
members with user defined default constructor.
Now imagine Number instead was a typedef for int. You would then add a
default constructor to Y, to make sure it gets initialize to zero. The
implicitly generated move constructors would still break the invariant
that "values.size() == length". to fix this issue one would therefore
also have to add the rule that implicit move constructor generation is
disabled, if Y itself has a default constructor.
>> There is *way* too much confusion and mis-information in this thread.
> I agree about the confusion.
Confusions and misunderstandings are fragile things though, they
disappears when confronted with clear argumentation that makes sense
of the situation. One could argue that the current rules cause some of
this confusion. The cases where copy semantics silently changes into
move semantics are subtle, and what types of examples these change
matters for is not well understood.
Thanks, Patrik
Not exactly what I meant, but close. :-)
The decision on move or copy is generally up to the designer of the
class. For many simple cases, copying is just as good as moving. In
that case the class would have a copy constructor and copy assignment,
but adding move assignment will not buy you anything.
In that case
y = std::move(x);
will be the best (fastest) way to assign x to y, even if it means
copying x's members to y.
If you really, really want to have a compiler error when there is no
move assignment defined, you could provoke one with
static_assert(std::is_move_assignable<T>::value, "error!!")
but why would you?
>
> That clears up most of my confusion, as there is some "method to the
> madness". To calm my unease though, I would like to have some kind
> of guidline that identifies why InvariantChecker like objects
> breaks, and why they deserve to break. So 1) the InvariantChecker
> object is using an alias to another object. Nothing bad there. 2)
> The InvariantChecker object depends on another object's state (not
> merely the other objects internal invariants). Nothing bad there
> either 3) but when the InvariantChecker object does 1 and 2 in its
> destructor, it might break (depending on context InvariantChecker
> is used in, which we have no control over). We now have silently
> introduced the unacceptable additional requirement that the
> referenced object has to have had its move semantics disabled. That
> we did this is not obvious just by looking at the class defintion
> of InvariantChecker. If we cant easily spot that InvariantChecker
> has this serious design flaw, how can hope to avoid writing such
> code? A good guidline would not depend on the context of how
> InvariantChecker is used, as that is outside of InvariantChecker's
> control. Algorithm developer can only give the compiler hints when
> move semantics is acceptable (by using std::move), not when move
> semantics is unacceptable (say by using some magic "return
> std::ccopy(val);").
>
No, that is still a problem. :-(
Making changes to the proposed standard right now is also a problem.
If nothing else, you would have to go through the 800 pages of the
standard library specification and see what the effects would be
there.
Bo Persson
> - - -
> Code Example Tweak#2 from David's post @http://cpp-next.com/archive/2010/10/implicit-move-must-go/
Thank you for posting the code you wanted to discuss. I honestly had
no idea that this is what you wanted to discuss.
-Howard
Actually, "seeing the difference" was my starting point of view.
Now I am trying to make a logical relation between those requirement
sets and special member functions.
This relation is important to me. It would also be very important
(and difficult to grasp) to those that are new to C++0x (or C++ itself).
> sets of the Standard Library are sets of expressions or initializations
> and a required semantics and/or post conditions.
> For example, the MoveConstructible requirements impose that a type T
> that satisfies this requirement set shall support an initialization
>
> T u = rv;
>
> and expression
>
> T(rv)
>
> where rv is an rvalue of T. This is *no* requirement for an existing
> move-constructor! It just means that above expressions are well-formed
> and that they satisfy further requirements. E.g. the fact that the
> destination of this construction is equivalent to the source before
> the construction had taken place. In fact, you can satisfy this
> requirement if you have a copy-constructor but no move constructor.
This is why I made a conclusion that X(const X &) should not be
treated independently of X(X &&), at least when looking at
the interface of a class. Different combinations of both
can decide whether a class satisfies some of those requirements.
> Here is an example of a type C which has a move-constructor but does
> *not* satisfy the MoveConstructible requirements:
>
> class C {
> int state;
> public:
> explicit C(int state) : state(state) {}
> C(C&&) : state(42) {}
> int get_state() const { return state; }
> bool operator==(C rhs) const { return state == rhs.state; }
> };
>
> C(1) c1;
> const int state1 = c1.get_state();
> C(2) c2 = std::move(c1);
> assert(c2.get_state() == state1); // Error!
This would be an awkward usage, and I don't wish to go there.
I always make copy (and recently move) operations do what they
are supposed to do. std::auto_ptr's weird copy-ctor and
its destiny are a fine warning against any awkward usages.
>> I regret that it wasn't possible to make these two declarations
>> into one declaration, even if it had two different implementations.
>> (Perhaps it could be an extension for C++1x?)
>
> This won't happen: Note that the special member functions are completely
> different from requirement sets.
Let me elaborate:
I start by wanting to make a MoveConstructible and MoveAssignable,
but not CopyConstructible and not CopyAssignable. This is my goal
as a designer of a class.
So now I have to write this into a class. This is why I'm trying
to make a relation between these requirements and special member
functions. The other reason would be to make a reverse, that is
to analyze if a class satisfies some of the requirement sets.
I understand that it is not only the interface that needs to
satisfy, but also preconditions and postconditions, but let's
assume that those are correct for each separate special member
function...
>> This would make it more comprehensible and more clean...
>
> It does not. There is much of a difference between concrete functions
> and supported expressions. A single expression requirement can sometimes
> be realized by different functions.
You are not talking about some Proxy objects and/or some special
conversions in order to satisfy these requirements, are you?
Because, the way I see it now, there are 5 ways to write a class
with value semantics (under the assumption of copy-ctor doing
copy and move-ctor doing move, without awkwardness):
X1::X1(const X & rhs);
X2::X2(const X & rhs);
X2::X2(X && rhs);
X3::X3(const X & rhs);
X3::X3(X && rhs) = delete;
X4::X4(const X & rhs) = delete;
X4::X4(X && rhs);
X5::X5(const X & rhs) = delete;
X5::X5(X && rhs) = delete;
X1 satisfies MoveConstructible and CopyConstructible
X2 satisfies MoveConstructible and CopyConstructible
X3 satisfies neither
X4 satisfies only MoveConstructible
X5 satisfies neither
Don't you think that those "dual-declarations" (where
each declaration can occur many lines apart) could also
be declared as single declarations without loss of
any meaning and without deviance from any current logic?
So, do you still find my reasoning as incorrect?
Once again, I'm not trying to find a 1-1 relation
between special member functions and requirement sets,
but only to find _some_ relation for the purpose
of design, analysis and overall understanding...
Thanks for the discussion.
--
Dragan
[..]
>> For example, the MoveConstructible requirements impose that a type T
>> that satisfies this requirement set shall support an initialization
>>
>> T u = rv;
>>
>> and expression
>>
>> T(rv)
>>
>> where rv is an rvalue of T. This is *no* requirement for an existing
>> move-constructor! It just means that above expressions are well-formed
>> and that they satisfy further requirements. E.g. the fact that the
>> destination of this construction is equivalent to the source before
>> the construction had taken place. In fact, you can satisfy this
>> requirement if you have a copy-constructor but no move constructor.
>
> This is why I made a conclusion that X(const X &) should not be
> treated independently of X(X &&), at least when looking at
> the interface of a class. Different combinations of both
> can decide whether a class satisfies some of those requirements.
But they are completely different entities and that is a good thing! I
can take advantage of that fact, because I can discriminate between
rvalues and lvalues as arguments. It is my own business, whether I
prefer to have a type that does not match Move/CopyConstructible, if I
still would prefer to realize a special expression behaviour. The core
language is and should not depend on some very special Library concepts.
The MoveConstructible and CopyConstructible concepts (sorry for falling
back to the term 'concept'. It is no longer valid as a part of the C++
language, but it still has the non-language meaning) are special cases.
Many (most?) daily-bread types are designed to satisfy these requirement
sets, but there are still valid reasons to design a class that
intentionally deviates from that.
>> Here is an example of a type C which has a move-constructor but does
>> *not* satisfy the MoveConstructible requirements:
>>
>> class C {
>> int state;
>> public:
>> explicit C(int state) : state(state) {}
>> C(C&&) : state(42) {}
>> int get_state() const { return state; }
>> bool operator==(C rhs) const { return state == rhs.state; }
>> };
>>
>> C(1) c1;
>> const int state1 = c1.get_state();
>> C(2) c2 = std::move(c1);
>> assert(c2.get_state() == state1); // Error!
>
> This would be an awkward usage, and I don't wish to go there.
> I always make copy (and recently move) operations do what they
> are supposed to do. std::auto_ptr's weird copy-ctor and
> its destiny are a fine warning against any awkward usages.
What I wanted to point out, is that C++ is a non-intrusive language. It
does *not* enforce a particular design choice. If *you* don't want to go
this route, don't follow it. But asking for changing the core language
to enforce a particular programming style for others is against the
spirit of C++ - and against mine ;-)
>>> I regret that it wasn't possible to make these two declarations
>>> into one declaration, even if it had two different implementations.
>>> (Perhaps it could be an extension for C++1x?)
>>
>> This won't happen: Note that the special member functions are completely
>> different from requirement sets.
>
> Let me elaborate:
>
> I start by wanting to make a MoveConstructible and MoveAssignable,
> but not CopyConstructible and not CopyAssignable. This is my goal
> as a designer of a class.
>
> So now I have to write this into a class. This is why I'm trying
> to make a relation between these requirements and special member
> functions. The other reason would be to make a reverse, that is
> to analyze if a class satisfies some of the requirement sets.
>
> I understand that it is not only the interface that needs to
> satisfy, but also preconditions and postconditions, but let's
> assume that those are correct for each separate special member
> function...
If you want to test, whether your type X satisfies some canonical valid
expressions, you can (static) assert taking advantage of some of the new
type traits, e.g. following the definition of X you could write:
class X {
...
};
static_assert(std::is_move_constructible<X>::value, "Bad boy");
static_assert(std::is_copy_constructible<X>::value, "Bad boy");
static_assert(std::is_move_assignable<X>::value, "Bad boy");
static_assert(std::is_copy_assignable<X>::value, "Bad boy");
>>> This would make it more comprehensible and more clean...
>>
>> It does not. There is much of a difference between concrete functions
>> and supported expressions. A single expression requirement can sometimes
>> be realized by different functions.
>
> You are not talking about some Proxy objects and/or some special
> conversions in order to satisfy these requirements, are you?
Not in particular. I only wanted to say that several required expression
may be realized by different means. I can satisfy The EqualityComparable
requirements by providing either a free binary operator== or as a member
function that overloads operator==. I can realize some implicit
conversion requirement by *either* providing a conversion function in my
source type *or* by providing a converting (non-explicit) constructor in
my destination type.
> Because, the way I see it now, there are 5 ways to write a class
> with value semantics (under the assumption of copy-ctor doing
> copy and move-ctor doing move, without awkwardness):
You need to define "value semantics", because...
> X1::X1(const X & rhs);
>
> X2::X2(const X & rhs);
> X2::X2(X && rhs);
>
> X3::X3(const X & rhs);
> X3::X3(X && rhs) = delete;
>
> X4::X4(const X & rhs) = delete;
> X4::X4(X && rhs);
>
> X5::X5(const X & rhs) = delete;
> X5::X5(X && rhs) = delete;
>
> X1 satisfies MoveConstructible and CopyConstructible
> X2 satisfies MoveConstructible and CopyConstructible
> X3 satisfies neither
> X4 satisfies only MoveConstructible
> X5 satisfies neither
.. which type would satisfy your criteria of a type with "value semantics"?
> Don't you think that those "dual-declarations" (where
> each declaration can occur many lines apart) could also
> be declared as single declarations without loss of
> any meaning and without deviance from any current logic?
That depends on your design aims. You could simply user-declare a
copy-constructor and you are done. This is no business of the core
language, it is just your own programming preference and may depend on
the requirements you want to impose on these types.
> So, do you still find my reasoning as incorrect?
> Once again, I'm not trying to find a 1-1 relation
> between special member functions and requirement sets,
> but only to find _some_ relation for the purpose
> of design, analysis and overall understanding...
I'm still not sure what you want to realize, but it is IMO very
important to ensure that the C++ core language remains a non-intrusive
language.
Sorry, if this reply does not answer your questions. If not, please
rephrase.
HTH & Greetings from Bremen,
Daniel Krügler
--
In this case there is no user-defined default constructor though. The invariant in question (length == values.size()) is established by the implicitly generated default constructor. So your new rule would not fix the example.
However, does this example really need fixing? As I posted in my reply to Dave Abrahams' blog post (linked below), the only time you're going to get code that breaks without breaking noisily, as a result of implicit move constructor generation, is when you're using objects of indeterminate value for something other than receiving assignments or being destroyed.
When would you actually ever do that?
http://cpp-next.com/archive/2011/02/w00t-w00t-nix-nix/
Here, I was refereeing to the explicit default constructor in Number
not the implicit in Y. The "Number::Number(unsigned x = 0)". While
maybe not technically called a default constructor, it allows for
default construction. So because Y has a member variable of type
Number that has an explicit constructor that allows it to be default
constructed ... if we took that as a a case where the compiler should
not generate a move constructor for Y, then the example should not
break. The current rules "looks" at Y and concludes that Y has no
invariants of its own, so a move constructor can safely be generated.
Y infact has an invariant, and we can make rules to spot this.
My other point was that AFAICT we could simplify this example and
still have it break (I think). If we get rid of the Numbers class, and
use "unsigned int length" directly in Y. Then we would obviously have
to initialize length in a default constructor in Y. The example would
still break as an incorrect move constructor would still be implicitly
generated.
So my point is that any (implicit or explicit) default constructor
might set up an invariant, that an move from might break. Even if the
default constructor is implicit and only calls its member variables
explicit default constructors (and if these themselves are implicit
rather than implicit then their member variables explicit default
constructors and so on and so forth).
I'll try and provide some example code later.
Thanks, Patrik
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3203.htm
>
> If the definition of a class X does not explicitly
> declare a move constructor, one will be implicitly
> declared as defaulted if and only if
> * X does not have a user-declared copy constructor,
> * X does not have a user-declared copy assignment operator,
> * X does not have a user-declared move assignment operator,
> * X does not have a user-declared destructor, and
> * the move constructor would not be implicitly defined as deleted.
>
> this example *still* breaks.
>
> Note again: I'm not saying implicit-move-ctors should necessarily be
> removed completely, I'm just saying that Dave *does* have a point.
>
> cheers,
> Martin
>
> - - -
> Code Example Tweak#2 from David's post @http://cpp-next.com/archive/2010/10/implicit-move-must-go/
I tweaked your tweaked example to simplify it a bit. The Number class
is not necessary to show the problem. Important to note is that this
example is not about an "implicit move constructor" breaking Z's
invariants but about a "implicitly generated move assignment operator"
doing so. std::remove after all move assigns, not move constructs.
VC10, that is the compiler I'm testing on, does not actually do
"implicitly generated move assignment operator" as specified by the
standard so I had to tweak your example (and mine) to get it to break
according to the standard. The relevant rules are:
If the definition of a class X does not explicitly declare a move
constructor, one will be implicitly declared as defaulted if and only
if:
- X does not have a user-declared copy constructor,
- X does not have a user-declared move constructor,
- X does not have a user-declared copy assignment operator is not user-
declared
- X does not have a user-declared destructor, and
- The move assignment operator would not be implicitly defined as
deleted.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3203.htm
<code>
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <assert.h>
struct Z {
Z() : len_(0) {
assert(len_ == vec_.size()); // invariant len_ == vec_.size()
established
}
/*VC10 does not implicitly generate this move assignment operator as
specified by standard so I had to explicitly simulated it.*/
Z& operator=(Z&& rhs) {
vec_=std::move(rhs.vec_);
len_=std::move(rhs.len_); //invariant broken. rhs.len_ !=
rhs.vec_.size()
return *this;
}
void add(char ch) {
vec_.push_back(ch);
++len_; //invariant len_ == vec_.size() maintained
}
friend std::ostream& operator<<(std::ostream& os, const Z& z);
friend bool operator==(const Z& lhs, const Z& rhs);
private:
unsigned int len_;
std::vector<char> vec_;
};
bool operator==(const Z& lhs, const Z& rhs) {
return lhs.vec_ == rhs.vec_;
}
std::ostream& operator<<(std::ostream& os, const Z& z) {
assert(z.len_ == z.vec_.size()); //invariant will be broken for Z's
moved from. z.len_ != z.vec_.size()
for(unsigned int i = 0; i < z.len_; ++i) {
os << z.vec_.at(i);
}
return os;
}
int main() {
Z za;
za.add('a');
za.add('a');
Z zb;
zb.add('b');
zb.add('b');
std::vector<Z> vec;
vec.push_back(za);
vec.push_back(zb);
vec.push_back(za);
std::remove(vec.begin(), vec.end(), zb); //invariants will break for
(objects equal to) zb
copy(vec.begin(), vec.end(), std::ostream_iterator<Z>(std::cout, "
")); //assert will fail.
}
</code>
Thinking about it more I think Sancho is right. That this example
breaks does not worry me so much, as these elements at the end have
always been in a bit of a twilight zone. I cant see their state being
used for other things than logging and debugging (as they will almost
always be destroyed or assigned too). Maybe someone else can see such
use-cases, or find examples that rely on other function that
std::remove. My worry is still with the other types of examples, the
examples that break because of implicit move return (Like the
InvariantChecker). But I would be happy if someone came up with a
clear rule of thumb to avoid such designs.
Thanks, Patrik
You were using the correct terms. You're absolutely right. I was wrong. Your rule would fix this particular case. However, whether this issue needs fixing is debatable and the fix is not free. You lose lots of implicit optimization opportunities. It could even be argued that your rule would break encapsulation to some degree by allowing changes in the implementation details of class1 (whether or not its constructors are implicitly-defined) to affect the behavior of any any class2 that has class1 data members.