class Widget {
public:
...
private:
std::vector<int> v1;
std::vector<int> v2;
mutable std::size_t cachedSumOfSizes;
mutable bool cacheIsUpToDate;
void checkInvariant() const
{ assert(!cacheIsUpToDate || cachedSumOfSizes == v1.size()+v2.size()); }
};
Assume that checkInvariant is called at the beginning and end of every public
member function. Further assume that the class declares no copy or more
operations, i.e., no copy or move constructor, no copy or move assignment
operator.
Suppose I have an object w where v1 and v2 have nonzero sizes, and
cacheIsUpToDate is true. Hence cachedSumOfSizes == v1.size()+v2.size(). If w
is copied, the compiler-generated copy operation will be fine, in the sense that
w's invariant will remain fulfilled. After all, copying w does not change it in
any way.
But if w is moved, the compiler-generated move operation will "steal" v1's and
v2's contents, setting their sizes to zero. That same compiler-generated move
operation will copy cachedSumOfSizes and cacheIsUpToDate (because moving
built-in types is the same as copying them), and as a result, w will be left
with its invariant unsatisfied: v1.size()+v2.size() will be zero, but
cachedSumOfSizes won't be, and cacheIsUpToDate will remain true.
When w is destroyed, the assert inside checkInvariant will fire when it's called
from w's destructor. That means that the compiler-generated move operation for
Widget broke the Widget invariant, even though the compiler-generated copy
operations for Widget left it intact.
The above scenario suggests that compiler-generated move operations may be
unsafe even when the corresponding compiler-generated copy operations are safe.
Is this a valid analysis?
Scott
--
* C++ and Beyond: Meyers, Sutter, & Alexandrescu, Oct. 24-27 near Seattle
(http://cppandbeyond.com/)
* License my training materials for commercial (http://tinyurl.com/yfzvkp9) or
personal use (http://tinyurl.com/yl5ka5p).
As far as it goes, it seems so, yes, at least wrt. n3090 which is the draft I have.
A move from an object X leaves X as zombie where there's logically nothing left
to destroy, and class needs to be designed to deal with it, in order to deal
properly with it.
And so, given that & my general wait-n-see ignorance of C++0x, I'm surprised to
hear that a move constructor is implicitly generated (n3090 §12.8/10). It breaks
existing code. It should not have been done.
HOWEVER, it does not seem to be a problem with actual compilers for Windows.
A simpler example than yours, with explicitly declared move constructor:
<code>
#include <assert.h>
#include <string>
#include <iostream>
class Blah
{
private:
std::string blah;
public:
Blah(): blah( "blah" ) {}
Blah( Blah&& other ): blah( std::move( other.blah ) )
{
std::cout << "[" << other.blah << "]" << std::endl;
}
~Blah() { assert( blah == "blah" ); }
};
void foo( Blah&& a )
{
Blah* p = new Blah( std::move( a ) );
delete p;
}
int main()
{
foo( Blah() );
}
</code>
<example>
C:\test> g++ --version
g++ (TDM-2 mingw32) 4.4.1
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
C:\test> g++ -std=c++0x x.cpp
C:\test> a
[blah]
C:\test> cl
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
usage: cl [ option... ] filename... [ /link linkoption... ]
C:\test> cl /nologo /EHsc /GR /Zc:forScope,wchar_t x.cpp
x.cpp
C:\test> x
[]
Assertion failed: blah == "blah", file x.cpp, line 15
C:\test> _
</example>
From the above, evidently MSVC 10.0 implements move constructors, while g++
4.4.1 does not, or it doesn't have an optimized std::string.
But when the move constructor definition is commented out, the assert does not
kick in, indicating that a move constructor is not automatically generated:
<example>
C:\test> cl /nologo /EHsc /GR /Zc:forScope,wchar_t x.cpp
x.cpp
C:\test> x
C:\test> _
</example>
So perhaps the standard could just be amended to reflect current practice? <g>
Cheers & hth.,
- Alf
--
blog at <url: http://alfps.wordpress.com>
Moves don't leave objects in "a zombie state." They must leave objects in a
state where their invariants are satisfied. Note that not all objects being
moved from will be destroyed immediately thereafter. Consider:
std::thread t1([]{ doSomething(); };
std::thread t2(std::move(t2)); // move t2's state to t1
At this point, t1 has been moved from, but it still exists and can still be
used. It will be destroyed at the end of its scope, as usual, but until then,
it can have any std::thread member function invoked on it. I wouldn't call t1 a
zombie, but I agree that whatever state it has, the class must be designed to
deal with it.
> HOWEVER, it does not seem to be a problem with actual compilers for
> Windows.
I don't know of any compiler for Windows that implements the part of draft C++0x
pertaining to the implicit generation of move operations, but many draft C++0x
features are currently available in gcc 4.5 and MSVC 10. Check out
http://www.aristeia.com/C++0x/C++0xFeatureAvailability.htm .
> A simpler example than yours, with explicitly declared move constructor:
I think the fundamental problem here is that a copy operation, because it
doesn't modify its source, can't invalidate a class invariant, but a move
operation can. Unless, of course, I'm overlooking something.
Arghh, typing too fast. I meant:
std::thread t2(std::move(t1)); // move t1's state to t2
Yes, I think so. Thanks for pointing out this example. Actually, I've
recently written a class with a member of type vector<deque<float> >
where I also keep track of the minimum deque length and how many
deques have this minimum length. But I don't check this invariant
prior to destruction so it would only fail if the moved-from object is
still used without any kind of "re-initialization".
I guess the important question is: Are these tolerable corner cases?
Under the current rules, a solution for your example doesn't require
writing your own move operations or deleting them. It could look like
this:
class Widget {
public:
...
private:
std::vector<int> v1;
std::vector<int> v2;
mutable std::size_t cachedSumOfSizes;
mutable replace_on_move<bool,false> cacheIsUpToDate;
...
};
where replace_on_move<bool,false> is a class that wraps a bool and
sets it to false when it is moved from. I guess such a class template
would come in handy.
Cheers!
SG
Zombie does not mean indeterminate. It means non-usable after being logically
destroyed. You have that state for most Java objects (and in general for object
in garbage collected languages). You can view it as the desired class invariant
D augmented with "or zombie" Z, so that the effective class invariant is D||Z.
The augmented class invariant is satisfied, it's just very impractical because
it has to be checked for at each operation; ideally you'd want just D.
Thus, move semantics does not come at zero cost unless the class in question has
a natural "empty" state for objects as part of D.
std::string and std::vector are simple containers and have such state, other
classes may not have it naturally, and then after being moved from such an
object is zombie (satisfying only the augmented class invariant D||Z).
>> HOWEVER, it does not seem to be a problem with actual compilers for
>> Windows.
>
> I don't know of any compiler for Windows that implements the part of
> draft C++0x pertaining to the implicit generation of move operations,
> but many draft C++0x features are currently available in gcc 4.5 and
> MSVC 10. Check out
> http://www.aristeia.com/C++0x/C++0xFeatureAvailability.htm .
>
>> A simpler example than yours, with explicitly declared move constructor:
>
> I think the fundamental problem here is that a copy operation, because
> it doesn't modify its source, can't invalidate a class invariant, but a
> move operation can. Unless, of course, I'm overlooking something.
Huh. Pardon my french, but the above does not parse.
"destroyed" is a strong word that is typically associated with the
destruction of an object (ending its life-time). In my opinion neither
"non-usable" nor "logically destroyed" are appropriate terms here.
Either, you're not familiar with the idea of how move semantics is
supposed to work or you have a funny definition of "non-usable" and
"logically destroyed" in mind.
Take a string class as example:
class string {
char *begin_;
char *end_;
char *capacity_;
public:
...
};
with the invariant
(1) all pointers are zero
OR (2) all pointers point to elements of the same array
with begin_ < end_ <= capacity_ and the array
is owned by the object.
Empty string values can be represented with (1) and non-empty string
values with (2). A reasonable implementation of a move constructor
just copies the pointers and sets the pointers in the object we "move
from" to zero, so it represents an empty string value. This object is
still in a "usable" state and I wouldn't describe this state as
"logically destroyed". It has obviously been mutated -- still obeying
its invariant but nothing happened that would qualify as "destruction"
in terms of the object's life-time.
> You have that state for most Java objects (and in general for object
> in garbage collected languages). You can view it as the desired class invariant
> D augmented with "or zombie" Z, so that the effective class invariant is D||Z.
> The augmented class invariant is satisfied, it's just very impractical because
> it has to be checked for at each operation; ideally you'd want just D.
>
> Thus, move semantics does not come at zero cost unless the class in question has
> a natural "empty" state for objects as part of D.
>
> std::string and std::vector are simple containers and have such state, other
> classes may not have it naturally, and then after being moved from such an
> object is zombie (satisfying only the augmented class invariant D||Z).
Then, you've written a very bad move constructor. You're not supposed
to let objects deteriorate in a way that makes them useless and only
ready for actual destruction!
> > I think the fundamental problem here is that a copy operation, because
> > it doesn't modify its source, can't invalidate a class invariant, but a
> > move operation can. Unless, of course, I'm overlooking something.
>
> Huh. Pardon my french, but the above does not parse.
A compiler-generated copy construction can, of course, invalidate
invariants as well. In generall, we call this a bug. See the example
from above. Without a user-defined copy constructor the last part of
the invariant is violated for both objects (source and target),
namely, "the [pointed-to] array is owned by the object". So, we could
reasonably say Scott's example class is just buggy in the sense that
"the rule of three" could be renamed to "the rule of five".
From what I can tell it really boils down to a trade-off. I like
implicitly generated move operations when they're correct. Obviously,
the current rules could break old classes and they could introduce
some gotchas. The question is, are these "tolerable corner cases" or
do we need more restrictive rules? For example, avoiding implicitly
generated move operations in case there are private data members. But
this additional restriction would require users to explicitly
"default" move operations in many classes. That's also undesirable in
my opinion.
Cheers!
SG
No, you need to get out of C++98 in order to discuss wider language features.
"Destroy" is a common term and method name for logical destruction in languages
where that operation has been necessary (it hasn't been in C++ until now).
I'm including Java and C#; you might care to look it up.
> In my opinion neither
> "non-usable" nor "logically destroyed" are appropriate terms here.
> Either, you're not familiar with the idea of how move semantics is
> supposed to work or you have a funny definition of "non-usable" and
> "logically destroyed" in mind.
Hm, either you're not familiar with the the point of move semantics or you have
a funny idea of usability and are not grasping the idea of logical destruction.
Moving means pilfering resources. That's the point, it's all about efficiency.
Moving without efficiency is pointless, extra work for no gain. And not all
classes can support pilfering of resources (the efficiency aspect that is the
reason for move semantics) and leave usable objects around. For at least some
classes instances need resources in order to be usable, in the sense of
operations succeeding and actually doing things.
The best that can be done in the general case is an artificial nullstate. You
can articially define an object in an articial nullstate as "usable" --
well-defined errors on nearly all ops, like a std::whateverstream in error state
-- but that's just wordplay. It's then a zombie.
That does not mean that I'm advocating zombies.
I'm just pointing out that that's a logical consequence of supporting move
semantics where the natural class invariant lacks a natural empty state.
There is no way around a logical consequence.
> Take a string class as example:
>
> class string {
> char *begin_;
> char *end_;
> char *capacity_;
> public:
> ...
> };
>
> with the invariant
>
> (1) all pointers are zero
> OR (2) all pointers point to elements of the same array
> with begin_< end_<= capacity_ and the array
> is owned by the object.
A string class is a class with a natural empty state as part of its normal class
invariant.
What you should look for is an example that doesn't have that.
Like a fixed size matrix implemented in terms of std::vector, say.
> Empty string values can be represented with (1) and non-empty string
> values with (2). A reasonable implementation of a move constructor
> just copies the pointers and sets the pointers in the object we "move
> from" to zero, so it represents an empty string value. This object is
> still in a "usable" state and I wouldn't describe this state as
> "logically destroyed". It has obviously been mutated -- still obeying
> its invariant but nothing happened that would qualify as "destruction"
> in terms of the object's life-time.
Right, it's a class that has a natural empty state.
That is not the general case.
It's not the droid you should be looking for.
>> You have that state for most Java objects (and in general for object
>> in garbage collected languages). You can view it as the desired class invariant
>> D augmented with "or zombie" Z, so that the effective class invariant is D||Z.
>> The augmented class invariant is satisfied, it's just very impractical because
>> it has to be checked for at each operation; ideally you'd want just D.
>>
>> Thus, move semantics does not come at zero cost unless the class in question has
>> a natural "empty" state for objects as part of D.
>>
>> std::string and std::vector are simple containers and have such state, other
>> classes may not have it naturally, and then after being moved from such an
>> object is zombie (satisfying only the augmented class invariant D||Z).
>
> Then, you've written a very bad move constructor. You're not supposed
> to let objects deteriorate in a way that makes them useless and only
> ready for actual destruction!
Or ready for re-initialization, that's a common technique in GC languages where
such objects abound.
You're right that that's Bad.
Wrt. move semantics you might classify classes as
A) Trivially supporting move semantics, an automatically generated move
constructor does the job.
B) Needing explicit support. This was the case with Scott's example class
with caching. It just needed a defined move constructor.
C) Not naturally supporting move semantics: no natural empty state, move
semantics support would introduce artifical zombie state.
I hope you're not proposing that all designs should be limited to classes that
are naturally compatible with move semantics, (A) and (B).
The presence of (C)-like classes in many/most designs is why the draft's (the
one I looketh at) /automatically generated/ move constructor is a very bad idea.
It's a trade-off that the programmer should explicitly have to make, because an
automatically generated move constructor means that those objects need to be
handled with extreme care. The default is the wrong way.
You'll probably agree with that when you've thought about it a little.
>>> I think the fundamental problem here is that a copy operation, because
>>> it doesn't modify its source, can't invalidate a class invariant, but a
>>> move operation can. Unless, of course, I'm overlooking something.
>>
>> Huh. Pardon my french, but the above does not parse.
>
> A compiler-generated copy construction can, of course, invalidate
> invariants as well. In generall, we call this a bug. See the example
> from above. Without a user-defined copy constructor the last part of
> the invariant is violated for both objects (source and target),
> namely, "the [pointed-to] array is owned by the object". So, we could
> reasonably say Scott's example class is just buggy in the sense that
> "the rule of three" could be renamed to "the rule of five".
>
> From what I can tell it really boils down to a trade-off. I like
> implicitly generated move operations when they're correct. Obviously,
> the current rules could break old classes and they could introduce
> some gotchas.
Yes.
> The question is, are these "tolerable corner cases"
No, they're intolerable common cases, and as you note above, they're in
/existing code/.
Recompile with C++0x and you break things.
> or
> do we need more restrictive rules? For example, avoiding implicitly
> generated move operations in case there are private data members. But
> this additional restriction would require users to explicitly
> "default" move operations in many classes. That's also undesirable in
> my opinion.
Why? As Scott's example shows, default move semantics does the Wrong Thing for
non-trivial classes, for classes that have class invariants. I would not be
surprised if that means for most classes.
<snip>
> The above scenario suggests that compiler-generated move operations may be
> unsafe even when the corresponding compiler-generated copy operations are safe.
> Is this a valid analysis?
I believe so.
I don't have much to add that others haven't already said, except to
add a reference link for those who would like to dive deeper into how
we got here:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3053.html
-Howard
As for not getting the move semantics concept: I'm well aware of the
rules of the language in that regard. The rest is just a matter of
terminology and point of view (me not liking your zombie term). Of
course, as a class author, you're free to define any semantics you
want. The trouble I see here is in the word "use" or "non-usable".
It's not exactly clear what you consider a "use" and what not. I'm
guessing that operator= would not qualify as "use" but as some kind of
"re-initialization" that establishes again the "natural invariant" (in
case the source was not a "zombie").
What I don't like about this is the "double standard". You're making
the distinction between "natural invariant" (that is not necessarily
valid in case of a "zombie state") and the actual class invariant
(including the "zombie state") that is supposed to be valid during the
whole object's life-time. I see no point in this distinction between
"natural null state" and "zombie null state". For all I care there is
a single invariant that includes the state you refer to as "zombie
state" or "null state". Now, if you define some operations to be
illegal in this state or not is up to you as a class designer. At the
very least this state should not make any differences in terms of
assignability (which could arguably be considered a "use").
> >> Huh. Pardon my french, but the above does not parse.
>
> > A compiler-generated copy construction can, of course, invalidate
> > invariants as well. In generall, we call this a bug. See the example
> > from above. Without a user-defined copy constructor the last part of
> > the invariant is violated for both objects (source and target),
> > namely, "the [pointed-to] array is owned by the object". So, we could
> > reasonably say Scott's example class is just buggy in the sense that
> > "the rule of three" could be renamed to "the rule of five".
>
> > From what I can tell it really boils down to a trade-off. I like
> > implicitly generated move operations when they're correct. Obviously,
> > the current rules could break old classes and they could introduce
> > some gotchas.
>
> Yes.
>
> > The question is, are these "tolerable corner cases"
>
> No, they're intolerable common cases, and as you note above, they're in
> /existing code/.
Existing code, yes.
"intolerable common cases", I don't know.
> > or
> > do we need more restrictive rules? For example, avoiding implicitly
> > generated move operations in case there are private data members. But
> > this additional restriction would require users to explicitly
> > "default" move operations in many classes. That's also undesirable in
> > my opinion.
>
> Why?
Because, obviously, I like not having to write
myclass(myclass&&) = default;
myclass& operator=(myclass&&) = default;
in almost all my classes to make them "move-enabled".
> As Scott's example shows, default move semantics does the Wrong Thing for
> non-trivial classes, for classes that have class invariants. I would not be
> surprised if that means for most classes.
I would be surprized if that means "for most classes". The compiler
WON'T implicitly declare a move ctor for classes with user-defined
copy ctor as it is. I don't think there are lots of classes without
user-defined copy ctor where the implicitly generated move ctor would
break the program. I don't know for sure. But I think you're
overestimating the severity of this a little.
Cheers!
SG
<nitpick>
Fwiw, a "user declared" copy constructor is sufficient to inhibit the
implicitly defined move constructor. I.e.
Widget(const Widget&) = default;
is sufficient. (the above is considered a declaration, not a
definition).
</nitpick>
--
I've been exploring the ramifications of Scott's example:
When Widget is used in generic std::code, such as vector<Widget> and
std::algorithm(sequence of Widgets), I believe it will continue to
work as in C++03, only faster /almost/ all of the time.
Explanation: The following members of Widget are implicitly defined:
Widget();
Widget(const Widget&);
Widget& operator=(const Widget&);
Widget(Widget&&);
Widget& operator=(Widget&&);
~Widget();
Implicitly defined members will not perform the invariant check at
their beginning and end. In almost all std-defined generic code, a
moved-from Widget will have exactly one of the following operations
applied to it:
Widget& operator=(const Widget&);
Widget& operator=(Widget&&);
~Widget();
Except for ~Widget(), these operations will restore the invariant. In
the case of ~Widget(), the invariant likely won't matter. However if
~Widget() is defined with an assertion check, then we have a noisy
error that can be corrected.
Example: One can insert into, and erase from vector<Widget>. Though
there will be intermediate stages of broken invariants of Widget,
these will not be exposed unless the insert throws. After a normally-
ending erase and insert, all Widgets in the vector will have satisfied
invariants. Similarly for all std::algorithms except for remove and
remove_if. And even in those, the typical idiomatic usage won't
expose a Widget with a broken invariant:
v.erase(remove(v.begin(), v.end(), w), v.end()); // All Widgets
in v still have intact invariants
In summary: Yes, there is breakage here. But I think it will be
uncommonly rare. Not only do you need a class like Scott's Widget,
but you also need to do something with a moved-from value besides
destruct it or assign it a new value. And this would have to happen
without an explicit move (since move doesn't exist in C++03):
vector<Widget>::iterator i = remove(v.begin(), v.end(), w);
if (i != v.end())
i->checkInvariant(); // bang, you're dead!
Thus I don't find Scott's example a show stopper, considering the
benefits that special move members bring (everything is an engineering
tradeoff). After all, even the addition of features as innocuous (and
as necessary) as decltype and static_assert have the potential to
break existing code (all existing code using those spellings).
I continue to believe the benefits outweigh the risks for special move
members. But no doubt, when upgrading to C++0X, thorough testing is
warranted, just as it is for any other change in your development
environment.
-Howard
Yes, but we have two decades of experience in identifying classes where the
compiler-generated copy operations would not behave correctly. To me, the
interesting thing isn't that implicitly-generated copy operations can be
incorrect (that's old news), but that implicitly-generated move operations can
be incorrect even when implicitly-generated copy operations are fine. In other
words, our intuition from C++98 wrt compiler-generated functions is insufficient
in the world of C++0x.
We have developed good guidelines regarding when users need to define their own
copy operations, e.g., when classes have pointer data members, when classes have
user-defined destructors, etc. What are the corresponding guidelines for when
users should define their own move operations? Saying "when the
compiler-generated versions would be incorrect" is hardly helpful.
Given that C++0x now supports defaulted special functions, I'm inclined to think
that a potentially useful rule is simply "Always declare copy and move
operations." If the compiler-generated versions would be okay, just say so:
class Widget {
public:
Widget(const Widget&) = default;
Widget(Widget&&) = default;
Widget& operator=(const Widget&) = default;
Widget& operator=(Widget&&) = default;
...
};
This involves a certain amount of syntactic noise for simple classes, but it has
the advantage that both human readers and source code analysis tools can verify
that the person writing the class has thought about and verified that the
compiler-generated versions do the right thing. It also addresses the
longstanding C++ question about whether users should declare their own copy
operations even when the compiler-generated versions would do the right thing.
With this rule, the answer is yes.
To some degree, the syntactic noise can be muted via macro:
class Widget {
DEFAULT_COPY_AND_MOVE_ARE_OKAY(Widget);
...
};
Unfortunately, any temporaries created for the insert may not. Consider:
std::vector<Widget> vw;
vw.push_back(Widget(x, y, z)); // asserts during destruction of temporary
> the case of ~Widget(), the invariant likely won't matter. However if
> ~Widget() is defined with an assertion check, then we have a noisy
> error that can be corrected.
I'm not sure how to correct this noisy error. We don't want to remove the
invariant check from ~Widget, because presumably it's there to detect corrupted
objects. I suppose we could do
vw.push_back((Widget&)Widget(x, y, z)); // force copy instead of move
but that seems pretty obscure.
> In summary: Yes, there is breakage here. But I think it will be
> uncommonly rare.
I agree that the problem is likely to be quite uncommon. But given the size of
the body of existing code that the committee trots out as its standard argument
for avoiding introducing breaking changes (especially silent ones), it seems odd
that the decision was made to ask developers to verify that all their old code
will continue to work rather than to have them manually add defaulted move
operations to new code. Yes, I realize that this would mean that old classes
would not magically benefit from move semantics when compiled with a C++0x compiler.
On the plus side, I suppose, static analysis tools can identify classes where
move operations will be automatically generated, so at least developers have a
way to find out where they need to check things.
You've lost the discussion already when it is the terminology you object to.
"Zombie" is a somewhat derogatory term.
That's because it is undesirable feature: it's a good term.
> Of
> course, as a class author, you're free to define any semantics you
> want. The trouble I see here is in the word "use" or "non-usable".
> It's not exactly clear what you consider a "use" and what not. I'm
> guessing that operator= would not qualify as "use" but as some kind of
> "re-initialization" that establishes again the "natural invariant" (in
> case the source was not a "zombie").
You're guessing correctly. No-one designs object for the purpose of being
assignable. If an object is assignable then that is in support of whatever the
object's purpose is.
> What I don't like about this is the "double standard". You're making
> the distinction between "natural invariant" (that is not necessarily
> valid in case of a "zombie state") and the actual class invariant
> (including the "zombie state") that is supposed to be valid during the
> whole object's life-time.
Right, it's undesirable.
> I see no point in this distinction between
> "natural null state" and "zombie null state".
That's pretty stupid, sorry.
One can't discuss things without aknowledging their existence.
For example, you provided an example where the distinction did not matter.
That's a fallacy if you understood it. Now you snipped a more relevant example,
and that's a fallacy because there's no chance that you haven't understood it by
now.
> For all I care there is
> a single invariant that includes the state you refer to as "zombie
> state" or "null state".
Then you end up with Microsoft-like code where you don't know whether your
window object has been initialized or not yet. You fail to make the critical
distinction, what has to be checked for at every operation, but make an
irrelevant technical distinction instead, as if a class invariant is something
to be determined after-the-fact of design, that MS classes with nullstates have
good class invariants. They do not have good class invariants. In short, your
point of view of here is to avert your eyes from the most relevant aspect, and
that's, again, just plain stupid as a real view.
However, it can make sense as argumentative technique, muddying the waters.
And I suspect that's what you're doing, given the quoting and snipping.
> Now, if you define some operations to be
> illegal in this state or not is up to you as a class designer. At the
> very least this state should not make any differences in terms of
> assignability (which could arguably be considered a "use").
I'm sorry but that's silly. Do you /like/ to check at every operation whether
your object is in usable state? Do you like the resulting bugs? Have you any
experience at all dealing with such objects? Your argument is just gibberish.
>>>> Huh. Pardon my french, but the above does not parse.
>>
>>> A compiler-generated copy construction can, of course, invalidate
>>> invariants as well. In generall, we call this a bug. See the example
>>> from above. Without a user-defined copy constructor the last part of
>>> the invariant is violated for both objects (source and target),
>>> namely, "the [pointed-to] array is owned by the object". So, we could
>>> reasonably say Scott's example class is just buggy in the sense that
>>> "the rule of three" could be renamed to "the rule of five".
>>
>>> From what I can tell it really boils down to a trade-off. I like
>>> implicitly generated move operations when they're correct. Obviously,
>>> the current rules could break old classes and they could introduce
>>> some gotchas.
>>
>> Yes.
>>
>>> The question is, are these "tolerable corner cases"
>>
>> No, they're intolerable common cases, and as you note above, they're in
>> /existing code/.
>
> Existing code, yes.
> "intolerable common cases", I don't know.
You should know.
>>> or
>>> do we need more restrictive rules? For example, avoiding implicitly
>>> generated move operations in case there are private data members. But
>>> this additional restriction would require users to explicitly
>>> "default" move operations in many classes. That's also undesirable in
>>> my opinion.
>>
>> Why?
>
> Because, obviously, I like not having to write
>
> myclass(myclass&&) = default;
> myclass& operator=(myclass&&) = default;
>
> in almost all my classes to make them "move-enabled".
I doubt that almost all your classes are assignable.
If they are then your designs are pretty uncommmon ones.
Anyway, having to explicitly enable an /optimization/ that may be /incorrect/
for your class, is good, not bad.
And having it applied as default is bad, not good.
Correctness is more important than micro-efficiency, especially when said
micro-efficiency can be added by the programmer who has determined that it's safe.
>> As Scott's example shows, default move semantics does the Wrong Thing for
>> non-trivial classes, for classes that have class invariants. I would not be
>> surprised if that means for most classes.
>
> I would be surprized if that means "for most classes". The compiler
> WON'T implicitly declare a move ctor for classes with user-defined
> copy ctor as it is.
User define copy constructors are not necessarily as common as you think.
Classes built from parts that are smart about copying (including smart pointers)
don't need them.
> I don't think there are lots of classes without
> user-defined copy ctor where the implicitly generated move ctor would
> break the program.
You snipped one simple example.
Such examples abound.
Averting your eyes does not make them go away.
> I don't know for sure. But I think you're
> overestimating the severity of this a little.
Perhaps. It doesn't matter. The standard's default is the wrong way anyhow,
whether the code that's broken by it is 10% or 50%.
Here, Howard was assuming that the destructor was also compiler-
generated.
Maybe, the rules could be just a tad more restrictive. For example,
also inhibiting the generation of move operations in case there is a
user-declared DESTRUCTOR seems like a reasonable idea to me at the
moment.
> Consider:
> std::vector<Widget> vw;
> vw.push_back(Widget(x, y, z)); // asserts during destruction of temporary
This piece of code would be okay with the above rule change.
> > the case of ~Widget(), the invariant likely won't matter. However if
> > ~Widget() is defined with an assertion check, then we have a noisy
> > error that can be corrected.
>
> I'm not sure how to correct this noisy error. We don't want to remove the
> invariant check from ~Widget, because presumably it's there to detect corrupted
> objects.
The easy fix is to write your own move constructor that sets the
mutable cacheIsUpToDate member from the source to false. As
alternative, I suggested to replace the bool type with a class that
wraps a bool and is automatically set to false if you move from it
(see replace_on_move<bool,false>).
Cheers!
SG
"Scott Meyers" <Neve...@aristeia.com> wrote in message
news:i49el1$4b4$1...@news.albasani.net...
> objects. I suppose we could do
>
> vw.push_back((Widget&)Widget(x, y, z)); // force copy instead of move
>
> but that seems pretty obscure.
>
Since when is it valid to bind a non-const reference to an rvalue?
/Leigh
I assume you meant "vw.push_back((const Widget&)Widget(x, y, z));" instead.
:)
/Leigh
I'm disappointed that instead of following the links I supplied to
explain the motivation for this language feature you instead made
assumptions and then based on those incorrect assumptions disparaged
the hard work and long hours of many volunteers who have been doing
nothing more than try to improve the industry in which you make a
living.
The special move members were introduced to solve a correctness/
backwards compatibility problem. Not to get an automatic
optimization.
Here's the link again:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3053.html
The references N3053 derives from are just as important.
-Howard
Yes, thank you.
enum PositionState { empty, nought, cross };
class TicTacToeBoard
{
private:
std::vector< PositionState > positions_;
public:
TicTacToeBoard(): positions_( 9 ) {}
// Operations.
};
>> I agree that the problem is likely to be quite uncommon. But given the size of
>> the body of existing code that the committee trots out as its standard argument
>> for avoiding introducing breaking changes (especially silent ones), it seems odd
>> that the decision was made to ask developers to verify that all their old code
>> will continue to work rather than to have them manually add defaulted move
>> operations to new code. Yes, I realize that this would mean that old classes
>> would not magically benefit from move semantics when compiled with a C++0x compiler.
>
> I'm disappointed that instead of following the links I supplied to
> explain the motivation for this language feature you instead made
> assumptions and then based on those incorrect assumptions disparaged
> the hard work and long hours of many volunteers who have been doing
> nothing more than try to improve the industry in which you make a
> living.
Motivations good, final result bad. It's like Obama being Thorbjørned[1] into
getting the Nobel Peace Prise. Intentions of committee very good, I assure you.
> The special move members were introduced to solve a correctness/
> backwards compatibility problem. Not to get an automatic
> optimization.
Sorry, that statement is logically inconsistent, and to boot addresses only
motivations, not final result (which result is very bad). Inconsistencies: (1)
there would be no correctness problem without moves and (2) there would be no
move semantics except for the motivation of efficiency. Plus as mentioned (3),
the motivation only matters for evaluating and choosing a fix, not for
discussing whether there is a problem, which there is: it breaks code.
> Here's the link again:
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3053.html
>
> The references N3053 derives from are just as important.
I think it's irrelevant, and that what is relevant is that people apparently
have painted themselves into position corners, unable to see solutions.
I'm pretty sure that resistance against fixing has to do with simple fear that
the benefit of efficiency will not be conferred on existing code to the degree
that it could have been, said fear based on inability to see solution to that.
One simple solution is how C++ has worked until now: each compiler vendor
provides options to let the programmer decide to apply optimizations that break
general standard C++ code.
For example, as default for direct invocation Microsoft's compiler applies the
optimization of not supporting RTTI (dynamic_cast, typeid) and not supporting
exception stack unwinding. These aspects can be turned on by compiler switches.
In Microsoft's IDE it's opposite: the language features are there by default,
and can be turned off by clicking here and there in suitable places.
Given that this programmer-decides-by-tool-invocation approach, while
problematic, has worked for practical programming work until now, the same
approach for letting the programmer decide to apply move semantics by default to
old code should work in practice. Especially when done in a better way than
backward compatibility has forced Microsoft to (wrong defaults for direct
compiler invocation, there not even supporting standard 'main'). On the other
hand, forcing it on the programmer by language rules is another matter; new
language rules that break existing code in spades are just Very Very Bad.
Cheers & hth.,
- Alf
Notes:
[1] The term "Thorbjørned" was introduced by the New York Times, after the
Norwegian politican Thorbjørn Jagland, former PM and member of the Nobel Peace
Prise committee, who pushed strongly for Obama receiveing the prise, which Obama
did not want. It's sort of a play on Norwegian "bjørnetjeneste", a bear trying
to help a squirrel getting rid of a fly on the squirrel's nose, by swatting the
fly. Good intention, good motivation, bad result...
I've read the explanatory part of N3053. (I skipped the proposed normative
language.) My table at
http://www.aristeia.com/C++0x/C++0xFeatureAvailability.htm even refers to it.
I've also read the equivalent parts of N3044, N2904, N2855 (and possibly others
-- it's hard to keep track after a while). I really do try to do my homework
before posting.
As regards the correctness/backwards compatibility problem, I don't know what
problem you are referring to. Fundamentally, the move operations can be special
or not. In the CD (N2798), they were not. In the FCD (N3092), they are. I
would be grateful if you would point me to an explanation of what
correctness/backwards compatibility problem in the CD draft is solved by making
the move operations special.
If you would like to officially propose a solution, contact me
privately (with draft in hand) and I will help you get a paper
published.
-Howard
From N2904:
> In N2855=09-0045 Doug Gregor and Dave Abrahams identified a serious problem
> relating to the copy/move for std::pair (and by implication for essentially
> every data structure with two or more elements). ...
From N2855:
> This paper describes a problem with the move construction idiom that
> compromises the exception safety guarantees made by the Standard Library.
> In particular, well-formed C++03 programs, when compiled with the C++0x
> Standard Library, can no longer rely on the strong exception safety
> guarantee provided by certain standard library container operations.
> This change silently breaks existing user code.
-Howard
Also from N2855:
> The problem addressed by this paper is three-fold. First, under C++0x, some
> existing types such as pair<string,legacy_type> automatically acquire a
> throwing move constructor.
If there are no automatically-generated move constructors, how can this problem
arise?
First I should emphasize that std::pair is still under flux. The
library chapters of N3092 have not yet had time to react to N3053
(though that reaction is in progress as this is written).
As written in N3092 pair has a move constructor whether first and
second do or not. This is wrong, and is exactly the problem exposed
by N2855. If pair<std::string, legacy> throws,
vector<pair<std::string, legacy>>::push_back can no longer guarantee
strong exception safety. I.e. the pair<std::string, legacy> move
constructor throws an exception.
The intent of N3053, reflected in N3092, is that pair<std::string,
legacy> has only a copy constructor (which may throw), and not a move
constructor. But at the same time, the move constructor of
pair<std::string, int> should exist and be fast (and noexcept(true)).
I.e. pair<T1, T2> needs to default its move members (and the language
needs to define "default" such that it works in all the ways we want
it to, and not in the ways we don't want it to). We have extremely
talented people (Daniel Krügler) who have volunteered to fix pair up
for us. We need more volunteers like Daniel.
-Howard
Yes, and I understand when you say that the goal of making move operations
special is to address this problem. I can see how one could view this as a
backwards compatibility and correctness issue, since both vector and pair
already exist. What does not currently exist (in C++98/03), however, is pair's
move constructor, so I'm inclined to view that as the root cause of the problem.
That is, when you say,
> The special move members were introduced to solve a correctness/
> backwards compatibility problem. Not to get an automatic
> optimization.
what you really mean is that they were introduced to make it possible to
implement pair's move operations with the desired set of characteristics.
That's a perfectly valid goal, but I think it's misleading to characterize it as
a backwards compatibility problem. Making pair unmovable would avoid the problem.
I also think it's misleading to say the motivation is not to get an automatic
optimization. As you wrote
> The intent of N3053, reflected in N3092, is that pair<std::string,
> legacy> has only a copy constructor (which may throw), and not a move
> constructor. But at the same time, the move constructor of
> pair<std::string, int> should exist and be fast (and noexcept(true)).
Certainly that makes it sound like at least part of the motivation for making
move functions special is improved performance. Which hardly a surprise. The
whole motivation for move semantics in general is to improve performance.
The committee deals with hard problems. Finding a way to combine rvalue
references, move operations, special member functions, exception safety, thread
safety, C++98/03 and C++0x standard library specifications, and legacy code is
not for the faint of heart. You're to be commended for being one of the movers
and shakers in this regard, and I know you've devoted countless hours over
literally years to get move-related stuff to work. I also know that many other
members of the committee have devoted similar effort on this and other topics.
Nevertheless, I don't think it shows a lack of respect to suggest that the
(tentative) conclusion they came to in (draft) C++0x to make the move operations
special is questionable.
pair is just an example. Making every aggregate unmovable would avoid
the problem. Is such an action really desirable?
> I also think it's misleading to say the motivation is not to get an automatic
> optimization. As you wrote
>
> > The intent of N3053, reflected in N3092, is that pair<std::string,
> > legacy> has only a copy constructor (which may throw), and not a move
> > constructor. But at the same time, the move constructor of
> > pair<std::string, int> should exist and be fast (and noexcept(true)).
>
> Certainly that makes it sound like at least part of the motivation for making
> move functions special is improved performance. Which hardly a surprise. The
> whole motivation for move semantics in general is to improve performance.
>
> The committee deals with hard problems. Finding a way to combine rvalue
> references, move operations, special member functions, exception safety, thread
> safety, C++98/03 and C++0x standard library specifications, and legacy code is
> not for the faint of heart. You're to be commended for being one of the movers
> and shakers in this regard, and I know you've devoted countless hours over
> literally years to get move-related stuff to work. I also know that many other
> members of the committee have devoted similar effort on this and other topics.
> Nevertheless, I don't think it shows a lack of respect to suggest that the
> (tentative) conclusion they came to in (draft) C++0x to make the move operations
> special is questionable.
Jettisoning all move semantics in C++0X is definitely an option. If
this is what the industry desires, far be it from me to stand in the
way. Such a request will have to be in the form of a proposal to the C
++ committee (which I'm happy to facilitate). Personally I do not
think this would be to the advantage of the average C++ programmer.
However that is only a single vote. I would very much respect the
consensus of the industry, gathered by the most transparent means
possible.
-Howard
I'm not questioning move semantics. I'm a big fan. I think it's the most
important new feature in C++0x. I suggested that it might have been a more
reasonable option to avoid making move operations special.
Because it's a big part of my job to explain this stuff to people, I know that
many developers, once they hear about move semantics, intuitively believe the
move operations should be special. The close relationship to the copy operations
(which are special) suggest they should be. I firmly believe that if I
demonstrated to them that making them special could silently break legacy code
(something somebody recently asked about, hence this thread), most would accept
that the cost of manually declaring them was worth the trouble. If you were to
then demonstrate to them that there was also a cost in terms of compilers being
unable to move-optimize aggregates, I suspect some would change their opinion
again. And I suspect that all would recognize that the issue is substantially
thornier than it initially appears.
OK, thanks.
What's lacking in the enclosed is the detailed wording changes for paragraphs in
the standard.
But I think it would be wise seek your counsel about that... ;-)
Cheers,
- Alf
PS: This is an imperfect draft...
Very sorry for posting an HTML attachment to the group, I meant to send this by
mail to Howard and Scott.
However, it let me discover an apparent error in the HTML code, so perhaps it
was good for something!
Also, if you're able to see the HTML, please don't hesitate to comment.
Cheers, & sorry!
- Alf (nose down on floor in very humble position)
Question. How exactly is this going to be fixed up? Ideally, we would
like to define std::pair so that it has a move constructor if its
template argument types T1 and T2 both have move constructors, and
otherwise define std::pair so that it does not have a move
constructor. Thus, in some program, one instantiation of std::pair
will have a move constructor, and another will not have a move
constructor. I don't think this is possible as the user level, though
my knowledge of template hackery is limited. Moreover, even if some
bizarre combination of boost::enable_if et al would let this happen,
is it reasonable to expect all C++ developers to be as knowledgeable
about arcane template usage to define a simple class like std::pair?
Do you know how Daniel Krügler plans to fix this? I am most curious.
Given Dave Abrahams and Doug McGregors's (?) solution to the basic issue of
strong exception guarantee, consisting of simply requiring non-throwing move
constructors, the automatic generation of copy constructor and move constructor
handles it, that is, the rules (§12.8/10) do exactly what you want. If a member
of the class doesn't have a move constructor then no std::pair move constructor
is generated, in this case no problem. Attempts to move will then copy. If both
members have move constructors, then a move constructor is generated for the
std::pair, again no problem. Attempts to move will then move, with no exceptions
thrown.
All that the std::pair class has to do is to just rely on the automatically
generated constructors, not defining those constructors itself.
There are no legacy classes with defined move constructors.
Much less any legacy classes with defined move constructors that can throw.
I think it would be a good idea to throw some common sense people at these
problems instead of narrow field experts.
Cheers,
>
> Given Dave Abrahams and Doug McGregors's (?)
Sigh. Doug Gregor. You can look it up.
--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)
Thank you very much Alf. (I apparently just had a temporary brain
lapse. I knew all of that already.) That works perfectly and is quite
sensible.
Thanks.
I would had I had a slightly faster PC... ;-)
> Given that C++0x now supports defaulted special functions, I'm inclined to
> think that a potentially useful rule is simply "Always declare copy and
> move operations." If the compiler-generated versions would be okay, just
> say so:
>
> class Widget {
> public:
> Widget(const Widget&) = default;
> Widget(Widget&&) = default;
> Widget& operator=(const Widget&) = default;
> Widget& operator=(Widget&&) = default;
> ...
> };
>
> This involves a certain amount of syntactic noise for simple classes,
I don't like noise. Not in real life, even less in code. Noise drives away
attention, and sucks blood from the moment of entering the system.
The best form of the code is IMO when every character bings important
information, and there is nothing else.
> but it has the advantage that both human readers and source code analysis
> tools can verify that the person writing the class has thought about and
> verified that the compiler-generated versions do the right thing.
Well, I wouldn't build on it a few weeks after the quoted lines made it in.
The next guy inserts another data member, or changes a type, and can break
the analysis. Similar to the original example, it is not hard to change
class invariants in a way they will no longer work with defaults.
> It also addresses the longstanding C++ question about whether users should
> declare their own copy operations even when the compiler-generated
> versions would do the right thing. With this rule, the answer is yes.
>
> To some degree, the syntactic noise can be muted via macro:
>
> class Widget {
> DEFAULT_COPY_AND_MOVE_ARE_OKAY(Widget);
> ...
> };
This looks better, but really what it buys ofer having the same line in
comment? It does the same, and the auto checking tool can find it the same
way.
The current code base I work with has destructors declared in all classes.
most of them is certainly empty as it should. I don't like it at all.
(especially as it breaks my usual approach to rule of 3...
However I tend to agree with suggestion upstream that move ctors entering
the scene may shake up the environment and force some change. We did learn
to tame the copy problem (through several tactics), may easily turn out the
a new approach is needed and/or some old styles will fall on face.
I hope to preserve my was (covering vast majority of my classes/structs)
that states to *avoid* declaring any of the special functions. And deal
with realted problems using proper members and/or base classes. From the
few examples here it is not yet shaken, and SG's solution in the first
replies hopefully can be applied for other usual cases.
Presumably you're referring to SG's explicit move operations that set the object
to a logically empty state.
Combined with your "avoid" declaring any of the special functions, and that the
built-in moves don't (currently) zero things, that means using "move-aware"
smart pointers, perhaps even "move-aware" smart integers, and so on, and/or
prohibiting automatic generation of move ops by having a non-movable sub-object.
Not completely unreasonable, but it only applies to classes whose instances have
a natural empty state.
Cheers, & hth.,
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.
> Combined with your "avoid" declaring any of the special functions, and that the
> built-in moves don't (currently) zero things, that means using "move-aware"
> smart pointers, perhaps even "move-aware" smart integers, and so on, and/or
> prohibiting automatic generation of move ops by having a non-movable sub-object.
>
> Not completely unreasonable, but it only applies to classes whose instances have
> a natural empty state.
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".
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.
Cheers!
SG
I've tried to post to lang.std.c++ group, but it looks like my post
was either rejected, lost, or stuck in a very long queue. So I will
repeat if briefly here. I might be missing the bigger picture, but
that's what immediately came to mind.
My suggestion was to change the move semantics for built-in types from
copy to swap. You need to include references in this category also. If
that is done, invariants will be preserved and you would probably
never need to write user-defined move contructor or assignment
operator, because compiler generated one will be doing what's
necessary.
Gene
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)