Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Class invariants and implicit move constructors (C++0x)

5,355 views
Skip to first unread message

Scott Meyers

unread,
Aug 15, 2010, 12:07:06 AM8/15/10
to
Consider a class with two containers, where the sum of the sizes of the
containers is cached. The class invariant is that as long as the cache is
claimed to be up to date, the sum of the sizes of the containers is accurately
cached:

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).

Alf P. Steinbach /Usenet

unread,
Aug 15, 2010, 2:06:52 AM8/15/10
to
* Scott Meyers, on 15.08.2010 06:07:

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>

Scott Meyers

unread,
Aug 15, 2010, 4:00:44 AM8/15/10
to
Alf P. Steinbach /Usenet wrote:
> 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.

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.

Scott Meyers

unread,
Aug 15, 2010, 4:02:39 AM8/15/10
to
Scott Meyers wrote:
> Consider:
>
> std::thread t1([]{ doSomething(); };
> std::thread t2(std::move(t2)); // move t2's state to t1

Arghh, typing too fast. I meant:

std::thread t2(std::move(t1)); // move t1's state to t2

SG

unread,
Aug 15, 2010, 4:39:23 AM8/15/10
to
On 15 Aug., 06:07, Scott Meyers wrote:
> [...]

> 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?

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

Alf P. Steinbach /Usenet

unread,
Aug 15, 2010, 6:34:59 AM8/15/10
to
* Scott Meyers, on 15.08.2010 10:00:

> Alf P. Steinbach /Usenet wrote:
>> 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.
>
> 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.

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.

SG

unread,
Aug 15, 2010, 7:49:31 AM8/15/10
to
On 15 Aug., 12:34, "Alf P. Steinbach /Usenet" wrote:
> * Scott Meyers, on 15.08.2010 10:00:
>
> > 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.
> > [...]

>
> Zombie does not mean indeterminate. It means non-usable after being logically
> destroyed.

"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

Alf P. Steinbach /Usenet

unread,
Aug 15, 2010, 9:00:59 AM8/15/10
to
* SG, on 15.08.2010 13:49:

> On 15 Aug., 12:34, "Alf P. Steinbach /Usenet" wrote:
>> * Scott Meyers, on 15.08.2010 10:00:
>>
>>> 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.
>>> [...]
>>
>> Zombie does not mean indeterminate. It means non-usable after being logically
>> destroyed.
>
> "destroyed" is a strong word that is typically associated with the
> destruction of an object (ending its life-time).

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.

Howard Hinnant

unread,
Aug 15, 2010, 10:49:43 AM8/15/10
to
On Aug 15, 12:07 am, Scott Meyers <NeverR...@aristeia.com> wrote:
> Consider a class with two containers, where the sum of the sizes of the
> containers is cached.  The class invariant is that as long as the cache is
> claimed to be up to date, the sum of the sizes of the containers is accurately
> cached:

<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

SG

unread,
Aug 15, 2010, 11:42:25 AM8/15/10
to
On 15 Aug., 15:00, Alf P. Steinbach wrote:
> [...]

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

Howard Hinnant

unread,
Aug 15, 2010, 12:59:26 PM8/15/10
to

<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

Scott Meyers

unread,
Aug 15, 2010, 1:50:44 PM8/15/10
to
SG wrote:
> A compiler-generated copy construction can, of course, invalidate
> invariants as well.

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);
...
};

Scott Meyers

unread,
Aug 15, 2010, 3:19:56 PM8/15/10
to
Howard Hinnant wrote:
> 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.

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.

Alf P. Steinbach /Usenet

unread,
Aug 15, 2010, 3:54:53 PM8/15/10
to
* SG, on 15.08.2010 17:42:

> On 15 Aug., 15:00, Alf P. Steinbach wrote:
>> [...]
>
> 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).

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%.

SG

unread,
Aug 15, 2010, 4:01:10 PM8/15/10
to
On 15 Aug., 21:19, Scott Meyers wrote:
> Howard Hinnant wrote:
> > 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.
>
> Unfortunately, any temporaries created for the insert may not.
> [...]

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

Leigh Johnston

unread,
Aug 15, 2010, 4:13:22 PM8/15/10
to

"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

Leigh Johnston

unread,
Aug 15, 2010, 4:20:11 PM8/15/10
to
"Leigh Johnston" <le...@i42.co.uk> wrote in message
news:u-idnaoXFZP_1_XR...@giganews.com...

I assume you meant "vw.push_back((const Widget&)Widget(x, y, z));" instead.
:)

/Leigh

Howard Hinnant

unread,
Aug 15, 2010, 5:00:32 PM8/15/10
to
On Aug 15, 3:19 pm, Scott Meyers <NeverR...@aristeia.com> wrote:

> Howard Hinnant wrote:
> > 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.

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

Scott Meyers

unread,
Aug 15, 2010, 5:32:17 PM8/15/10
to
Leigh Johnston wrote:
> I assume you meant "vw.push_back((const Widget&)Widget(x, y, z));"
> instead. :)

Yes, thank you.

Alf P. Steinbach /Usenet

unread,
Aug 15, 2010, 6:11:32 PM8/15/10
to
* Howard Hinnant, on 15.08.2010 23:00:

> On Aug 15, 3:19 pm, Scott Meyers<NeverR...@aristeia.com> wrote:
>> Howard Hinnant wrote:
>>> In summary: Yes, there is breakage here. But I think it will be
>>> uncommonly rare.

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...

Scott Meyers

unread,
Aug 15, 2010, 6:35:56 PM8/15/10
to
Howard Hinnant wrote:
> 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

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.

Howard Hinnant

unread,
Aug 15, 2010, 7:02:42 PM8/15/10
to
On Aug 15, 6:11 pm, "Alf P. Steinbach /Usenet" <alf.p.steinbach

+use...@gmail.com> wrote:
> 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.

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

Howard Hinnant

unread,
Aug 15, 2010, 7:06:31 PM8/15/10
to
On Aug 15, 6:35 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
> 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.

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

Scott Meyers

unread,
Aug 15, 2010, 7:35:05 PM8/15/10
to
Howard Hinnant wrote:
> From N2855:
>
>> This paper describes a problem with the move construction idiom that
>> compromises the exception safety guarantees made by the Standard Library.

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?

Howard Hinnant

unread,
Aug 15, 2010, 9:04:20 PM8/15/10
to
On Aug 15, 7:35 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
> Howard Hinnant wrote:
> > From N2855:
>
> >> This paper describes a problem with the move construction idiom that
> >> compromises the exception safety guarantees made by the Standard Library.
>
> 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

Scott Meyers

unread,
Aug 15, 2010, 9:30:30 PM8/15/10
to
Howard Hinnant wrote:
> 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.

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.

Howard Hinnant

unread,
Aug 15, 2010, 9:47:20 PM8/15/10
to
On Aug 15, 9:30 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
> Howard Hinnant wrote:
> > 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.
>
> 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.

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

Scott Meyers

unread,
Aug 15, 2010, 10:31:30 PM8/15/10
to
Howard Hinnant wrote:
> On Aug 15, 9:30 pm, Scott Meyers <NeverR...@aristeia.com> wrote:
>> 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.

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.

Alf P. Steinbach /Usenet

unread,
Aug 15, 2010, 11:42:36 PM8/15/10
to
* Howard Hinnant, on 16.08.2010 01:02:

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...

implicit_move_constructor.html

Alf P. Steinbach /Usenet

unread,
Aug 16, 2010, 12:02:08 AM8/16/10
to
* Alf P. Steinbach /Usenet, on 16.08.2010 05:42:

> * Howard Hinnant, on 16.08.2010 01:02:
>> On Aug 15, 6:11 pm, "Alf P. Steinbach /Usenet"<alf.p.steinbach

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)

Joshua Maurice

unread,
Aug 16, 2010, 4:26:01 PM8/16/10
to
On Aug 15, 6:04 pm, Howard Hinnant <howard.hinn...@gmail.com> wrote:
> 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.

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.

Alf P. Steinbach /Usenet

unread,
Aug 16, 2010, 5:21:28 PM8/16/10
to
* Joshua Maurice, on 16.08.2010 22:26:

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,

Pete Becker

unread,
Aug 16, 2010, 5:29:10 PM8/16/10
to
On 2010-08-16 17:21:28 -0400, Alf P. Steinbach /Usenet said:

>
> 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)

Joshua Maurice

unread,
Aug 16, 2010, 5:31:24 PM8/16/10
to
On Aug 16, 2:21 pm, "Alf P. Steinbach /Usenet" <alf.p.steinbach

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.

Alf P. Steinbach /Usenet

unread,
Aug 16, 2010, 5:31:42 PM8/16/10
to
* Pete Becker, on 16.08.2010 23:29:

> On 2010-08-16 17:21:28 -0400, Alf P. Steinbach /Usenet said:
>
>>
>> Given Dave Abrahams and Doug McGregors's (?)
>
> Sigh. Doug Gregor. You can look it up.

Thanks.

I would had I had a slightly faster PC... ;-)

Balog Pal

unread,
Aug 17, 2010, 4:51:09 PM8/17/10
to
"Scott Meyers" <Neve...@aristeia.com>

> 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.

Alf P. Steinbach /Usenet

unread,
Aug 17, 2010, 5:48:16 PM8/17/10
to
* Balog Pal, on 17.08.2010 22:51:

> "Scott Meyers" <Neve...@aristeia.com>
>
>> 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,
[snip]

>
> 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.,

SG

unread,
Aug 17, 2010, 10:37:38 PM8/17/10
to
On 17 Aug., 23:48, Alf P. Steinbach wrote:
> * Balog Pal, on 17.08.2010 22:51:
>
> > 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.

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

Gene Bushuyev

unread,
Aug 18, 2010, 12:35:28 AM8/18/10
to
On Aug 15, 12:07 am, Scott Meyers <NeverR...@aristeia.com> wrote:
> Consider a class with two containers, where the sum of the sizes of the
> containers is cached. The class invariant is that as long as the cache is
> claimed to be up to date, the sum of the sizes of the containers is accurately
> cached:
>
> 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).

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

Alf P. Steinbach /Usenet

unread,
Aug 18, 2010, 1:06:06 AM8/18/10
to
* SG, on 18.08.2010 04:37:

> On 17 Aug., 23:48, Alf P. Steinbach wrote:
>> * Balog Pal, on 17.08.2010 22:51:
>>
>>> 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.
>
> 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".

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)

Alf P. Steinbach /Usenet

unread,
Aug 18, 2010, 2:35:24 AM8/18/10
to
* Alf P. Steinbach /Usenet, on 18.08.2010 07:06:

> * SG, on 18.08.2010 04:37:
>
> 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?
>


Hm, that thinking was *not* correct.

A defined copy constructor says that a copy may e.g. need to have a backpointer
to itself, and so also for moved object, hence no automatic move op generation
when there is defined copy constructor.

Posting late (or early, as it also was).


Cheers,

- Alf

SG

unread,
Aug 18, 2010, 5:04:20 AM8/18/10
to
On 18 Aug., 07:06, Alf P. Steinbach wrote:
>
> Also, crossing the t's, it would be "non-trivial" user defined destructor.

According to the FCD's definition of "trivial" there are no trivial
"user-provided" destructors. Also, virtual destructors are never
trivial. (see §12.4/3)

> Because many people (including myself) define destructors for other purposes
> than directly customizing destruction, e.g. to make a class polymorphic.

But how often do you need to copy/move objects of polymorphic classes?
I do like the idea of implicitly generated move ops. But in cases of
classes that need a virtual destructor I can live without implicit
move ops. If that makes the rules simpler, great.

> 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).

Since trivial already means something else, the wording might look
like this:

If the class definition 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 and
- X does not have any user-declared (copy/move) assignment
operators and
- X does not have a user-declared destructor OR it has an
in-class user-provided destructor with no statements and
- the move constructor would not be implicitly defined as deleted.

Similar for the move assignment operator:

If the class definition does not explicitly declare a move
assignment operator, one will be implicitly declared as defaulted
if and only if
- the copy assignment operator is not user-declared and
- X does not have any user-declared (copy/move) constructors and
- X does not have a user-declared destructor OR it has an
in-class user-provided destructor with no statements and
- the move assignment operator would not be implicitly defined as
deleted.

where "in-class user-provided empty destructor" is not supposed to
rule out virtual destructors.

> 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. :-)

Yes. I agree with you. :-)

Cheers!
SG

Scott Meyers

unread,
Aug 18, 2010, 3:43:11 PM8/18/10
to
Gene Bushuyev wrote:
> My suggestion was to change the move semantics for built-in types from
> copy to swap.

What do you swap with in a move constructor? The destination object doesn't
exist yet. (It's being constructed).

SG

unread,
Aug 23, 2010, 4:02:01 AM8/23/10
to
On 16 Aug., 05:42, Alf P. Steinbach wrote:
> Howard Hinnant, on 16.08.2010 01:02:
> > 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.
>
> OK, thanks.
> [...]

> PS: This is an imperfect draft...

Any updates you would like to share, Alf?

Cheers!
SG

Alf P. Steinbach /Usenet

unread,
Aug 23, 2010, 8:35:19 AM8/23/10
to
* SG, on 23.08.2010 10:02:

No. Howard stated that many experts on the committee, including Bjarne,
disagreed with something unspecified in this draft. But I think that was his own
interpretation of earlier writings on the issue, not that those people had read
the draft. He recommended I try it on as a defect report instead of as a paper,
but as far as I know there are no defect reports for a draft standard.

I think your suggestion else-thread, about not generating the move ops
automatically when there is a defined assignment op or defined destructor
(unless known that it has empty body), would be the best solution.

It might seem draconian but I don't think it would exclude much code needlessly,
and I think the Really Important aspect here is that moving is an optimization
and that an optimization shouldn't break things but should have the exact same
effect as without the optimization, only faster or less memory.


Cheers,

- Alf

Howard Hinnant

unread,
Aug 23, 2010, 10:22:34 AM8/23/10
to
On Aug 23, 8:35 am, "Alf P. Steinbach /Usenet" <alf.p.steinbach

+use...@gmail.com> wrote:
> * SG, on 23.08.2010 10:02:
>
> > On 16 Aug., 05:42, Alf P. Steinbach wrote:
> >> Howard Hinnant, on 16.08.2010 01:02:
> >>> 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.
>
> >> OK, thanks.
> >> [...]
> >> PS: This is an imperfect draft...
>
> > Any updates you would like to share, Alf?
>
> No. Howard stated that many experts on the committee, including Bjarne,
> disagreed with something unspecified in this draft. But I think that was his own
> interpretation of earlier writings on the issue, not that those people had read
> the draft.

My email records indicate that the "something unspecified" referred to
above was not in the draft, but an assertion made by Alf in a private
email to me that the issues of special move functions, and the
exception safety problem described in N2855 were orthogonal.

For whatever reasons (and I'm willing to accept half the blame), Alf
and I were unable to communicate with each other.

> He recommended I try it on as a defect report instead of as a paper,
> but as far as I know there are no defect reports for a draft standard.

Here is a quote I wrote to Alf.

> I can not forward a paper to core without wording. However if you would like to
> send your paper in as a core issue, that seems appropriate to me. Here is the
> latest core issues list:
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html
>
> Contact the author of that list and ask if you can submit your paper as a core
> issue.
>
> If you would prefer the paper route, I recommend developing wording by working
> with members of the CWG who have been working this area. Their names/emails are
> on the papers I have been referencing. I should point out that although your
> paper does point out a problem, it does not appear to offer a solution to the
> problem which these authors have been attempting to address.

If anyone has any better advice for Alf, or can state the above advice
more clearly, I would be obliged.

I can report that the subject was picked up and briefly discussed on
the internal core language mailing list. As far as I can tell, no
conclusions were reached. I do not know if a core issue was opened on
the subject, nor if someone decided to write a paper on it (with
proposed wording). However a mailing deadline has just passed and the
post-Rapperswil mailing will appear here:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/

some time later this week. If an issue was opened, or a paper
written, you'll see it then and there.

-Howard

SG

unread,
Aug 23, 2010, 2:12:29 PM8/23/10
to
On 23 Aug., 14:35, Alf P. Steinbach wrote:
> SG, on 23.08.2010 10:02:
>
> > Any updates you would like to share, Alf?

[snip]

> I think your suggestion else-thread, about not generating the move ops
> automatically when there is a defined assignment op or defined destructor
> (unless known that it has empty body), would be the best solution.

I also included user-declared a copy constructor in that list.
Currently, I like this approach. Though, I can't rule out that there
is something that I'm missing so I welcome other feedback. These are
rules that are just a tad more restrictive than the ones proposed in
N3053. And with these rules both Scott's example and yours would be
fine and not break in C++0x mode.

> It might seem draconian but I don't think it would exclude much code needlessly,
> and I think the Really Important aspect here is that moving is an optimization
> and that an optimization shouldn't break things but should have the exact same
> effect as without the optimization, only faster or less memory.

Right.


On 23 Aug., 16:22, Howard Hinnant wrote:
>
> My email records indicate that the "something unspecified" referred to
> above was not in the draft, but an assertion made by Alf in a private
> email to me that the issues of special move functions, and the
> exception safety problem described in N2855 were orthogonal.

This is how I see it. Please correct any mistakes:

N2855:
points out that throwing move constructors (without any additional
compiler-magic to cope with this) poses a problem in terms of
exception safety. Example: class template pair. Depending on the
template parameters it might or might not be a good idea to declare
a move constructor -- that is, if you want to avoid throwing move
constructors. Its proposed solution relies on "concepts", or more
specifically: a requires clause to conditionally support move ops
depending on the template parameters.

N2904:
operates also under the viewpoint that throwing move ops are bad.
The idea is to promote move ops to special functions and let the
compiler figure whether it is "safe" to generate them -- always
having aggregate-like types like pair<A,B> with no invariants
in mind which in my humble opinion is a dangerous way of thinking
here.

N3053:
Refinement of N2904. Slight rule changes.

N3050:
refinement of N2855. Now, throwing move ctors are deemed to be okay
if we get a little more compiler magic accessible via the noexcept
operator and the nothrow_xxx traits. With this in our hands we are
able to solve the exception safety issue by either suppressing
move ops via SFINAE and/or by allowing throwing moves by marking
them with noexcept(false) so that std::move_if_noexcept falls back
on a copy if we need the strong exception safety guarantee.

But we're left with possibly dangerous rules of N3053 that lead also
to implicitly generated move ops in cases where we DON'T have simple
aggregate-like types and where old code can break in C++0x mode.

If the rules are changed to be a little more restrictive like I
suggested in the August 18th post we get rid of most breaking examples
of old code (including Scott's and Alf's code), still have implicitly
generated move ops for many aggregate-like types and can solve the
problem pointed out by N2855 with the noexcept exception specification
and the traits we got since N3050. Since I proposed to disable
compiler-generated move ops in case any special functions are user-
declared and pair/tuple actually need to at least have user-defined
assignment operators so that "reference members" are correctly dealt
with, we would have to write a pair/tuple class template with user-
defined move ops like this:

template<class T, class U>
struct pair {
T first;
U second;

...

pair(pair && p) noexcept(
nothrow_constructible<T,T&&>::value
&& nothrow_constructible<U,U&&>::value )
: first (forward<T>(p.first))
, second(forward<U>(p.second))
{}

pair& operator=(pair && p) noexcept(
noexcept(first = forward<T>(p.first ))
&& noexcept(second = forward<U>(p.second)) )
{
first = forward<T>(p.first);
second = forward<U>(p.second);
}

...
};

Of course, if you don't want the move ops at all in cases they are not
exception-safe, we really need something like a requires-clause. I'd
be happy to have one without the whole concepts machinery, actually.
Just a requires-clause that accepts a compile-time bool constant that
depends on some template parameters. It doesn't give us "modular type
checking" in the sense that Doug Gregor explained it in one of his
concepts talks but it would be a newbie-friendly SFINAE replacement
and could be used on non-template members of class templates as well.
But I digress...

> I can report that the subject was picked up and briefly discussed on
> the internal core language mailing list. As far as I can tell, no
> conclusions were reached. I do not know if a core issue was opened on
> the subject, nor if someone decided to write a paper on it (with
> proposed wording). However a mailing deadline has just passed and the
> post-Rapperswil mailing will appear here:
>
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/
>
> some time later this week. If an issue was opened, or a paper
> written, you'll see it then and there.

Thanks for the update, Howard! I'm glad to hear that it as been
discussed at all.

Cheers!
SG

SG

unread,
Oct 21, 2010, 12:26:20 PM10/21/10
to
On 23 Aug., 20:12, SG wrote:
> On 23 Aug., 16:22, Howard Hinnant wrote:
> > [...]

I would like to point out this has been addressed by two (competing)
proposals:

[1] N3153: "Implicit Move Must Go" (D. Abrahams)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3153.htm

This is about disabling any implicit generation of move operations but
still allowing the =default syntax to avoid any C++98 compatibility
issues and possible, unanticipated traps ("so late in the game").

[2] N3174: "To move or not to move" (B. Stroustrup)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3174.pdf

This proposal is basically similar to what we came up with here (see
my August 18th post). User-declared copy/move functions (including the
=default and =delete syntax) as well as a user-declared destructor
indicate possible invariants and the idea is to suppress compiler-
generated copy/move operations altogether in this case. That is /
including/ implicit generation of copy. Yes, you read correctly.
Stroustrup is actually proposing to /deprecate/ implicit copy ctors
and copy assignments in these cases as well. And "deprecate" means,
it's still generated but it would be nice if a compiler warned about
it in C++0x mode. This way, the rules for implicit generation of copy
and move are similar, easy to remember and we get rid of many "rule of
three" traps, beginners like to step into...

Regarding Dave's std::remove example (see
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3153.htm ),
the rules of N3174 still might surprize a user. But we could also
argue that std::remove is not backwards compatible because it requests
the elements to move instead of copying them. We could also argue that
the user wrote buggy code if he/she really tries to "use" the objects
behind the end of the resulting range.

Cheers!
SG

Howard Hinnant

unread,
Oct 22, 2010, 12:34:34 PM10/22/10
to
On Oct 21, 12:26 pm, SG <s.gesem...@gmail.com> wrote:

> Regarding Dave's std::remove example (seehttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3153.htm),
> the rules of N3174 still might surprize a user. But we could also
> argue that std::remove is not backwards compatible because it requests
> the elements to move instead of copying them. We could also argue that
> the user wrote buggy code if he/she really tries to "use" the objects
> behind the end of the resulting range.

Imho C++98/03 specifies that the values behind the end of the
resulting range are valid but unspecified. Furthermore there are
highly motivating reasons for that position. I believe the following
is a valid C++03 optimization of remove:

pair<vector<int>, int>*
remove(pair<vector<int>, int>* __first, pair<vector<int>, int>*
__last,
const pair<vector<int>, int>& __value)
{
__first = std::find(__first, __last, __value);
if (__first != __last)
{
pair<vector<int>, int>* __i = __first;
while (++__i != __last)
{
if (!(*__i == __value))
{
swap(__first->first, __i->first);
__first->second = __i->second;
++__first;
}
}
}
return __first;
}

This works the same as the general remove algorithm in the range
[first, returned-iterator), but gives different results in the range
[returned-iterator, last). And it will execute O(N) faster. Clients
of such an implementation could use the values in [returned-iterator,
last) as long as that use did not assume what the precise values were
upon returning from remove. For example client code could inspect
each pair<vector<int>, int>, call clear() on pair.first, etc.

Defining a moved-from value in C++0X as a valid but unspecified value,
and defining remove to use move, is a backwards compatible change: In
both standards the client sees valid but unspecified values in the
range [returned-iterator, last).

-Howard

SG

unread,
Oct 23, 2010, 5:27:38 PM10/23/10
to
On 22 Okt., 18:34, Howard Hinnant wrote:

> On Oct 21, 12:26 pm, SG wrote:
>
> > Regarding Dave's std::remove example (see
> > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3153.htm ),
> > the rules of N3174 still might surprize a user. But we could also
> > argue that std::remove is not backwards compatible because it requests
> > the elements to move instead of copying them. We could also argue that
> > the user wrote buggy code if he/she really tries to "use" the objects
> > behind the end of the resulting range.
>
> Imho C++98/03 specifies that the values behind the end of the
> resulting range are valid but unspecified.  Furthermore there are
> highly motivating reasons for that position.  I believe the following
> is a valid C++03 optimization of remove:
> [...]

> Defining a moved-from value in C++0X as a valid but unspecified value,
> and defining remove to use move, is a backwards compatible change:  In
> both standards the client sees valid but unspecified values in the
> range [returned-iterator, last).

In other words, you're saying that Dave's code examples are
questionable because objects with unspecified values are
"used" (before they are reassigned or destructed). I agree. Not that
it makes any difference, but I'm currently in favor of Stroustrup's
proposal -- including the part about deprecating compiler-generated
copy operations in some cases.

Cheers!
SG

Howard Hinnant

unread,
Oct 23, 2010, 11:43:31 PM10/23/10
to

Actually no, Dave's example is correct. It is ok to use an
unspecified value as long as you do so in a way that does not violate
the type's invariants. Since you don't know the value of an
unspecified value, then the only things that you can do with such a
value are those things that do not have any preconditions on the
value's state.

For example: If you have a std::vector with an unspecified value, it
is ok to call clear() on it. clear() has no preconditions. But it is
not ok to call pop_back() on it. The vector might be empty, and thus
you would be violating a precondition of pop_back.

In Dave's example the C++03 version of Y has an invariant that
values.size() == 1. Even a Y with unspecified value has values.size()
== 1. In C++03, you should be able to index a Y with 0, even if the
value of Y is unspecified. Dave's argument is that if we give Y
implicit move members, those move members would leave a Y in a state
such that its invariant does not hold (values.size() == 0). And since
std::remove will expose such moved from members, it is possible for
this valid C++03 code to break under C++0X if Y gets implicit move
members. Dave's example is correct.

My post asserts that a C++0X move-based std::remove is backwards
compatible. However that assertion assumes that we do not break a
type's invariants. If we do break a type's invariants, then
std::remove isn't backwards compatible, and neither is any other code
that might expose valid but unspecified values.

For example if vector::insert-in-the-middle throws an exception, or if
std::sort throws an exception, moved-from values are again exposed.

I am not trying to argue either side of the "implicit move members"
debate. I'm trying to describe the role of move in algorithms, and
how it is a backwards compatible change as long as a moved-from value
is valid, though unspecified, which means that all invariants still
have to be intact.

I believe Dave's paper is correct: If we implicitly generate move, we
will have the potential for breaking code. Dave's paper aptly
demonstrates that implicit move comes with a cost, no matter what the
rules are for implicit move generation.

I also believe Bjarne's paper raises a valid point: Perhaps the
benefit outweighs the cost.

-Howard

Patrik Kahari

unread,
Oct 30, 2010, 6:35:31 PM10/30/10
to
Hi

Looking at Scott Meyers example, reading the comments here. and
thinking about move only objects or expensive to copy objects (like
mutex wrappers, framework singletons, etc). The following occurred to
me.

Are not all these proposals workarounds for the fact that the compiler
is calling the destructor for moved from (zombie) objects? And why do
we need the destructor to be called for such objects? It seems to me
all these problems would disappear if the destructor was not called
for moved from objects. It also seems to me to be the correct thing,
from an ownership point of a view. My reasoning below ..

The two following point are not obvious to me.

1) Why are destructor's called for moved from objects?

It seems fundamentally wrong to me. A move should transfers ownership
of one objects internal resources and the responsibilities of its
invariants from one object to another. Its the responsibility of the
destination objects destructor to tear down the resource built up in
the sources constructor. The source should not linger on. Why should
the source object destructor do anything, when it no longer own any of
its previous resources or has any of its invariant responsibilities?

A parallel is how today a destructor's does not clean up an object
that fails halfway through construction. Such objects never properly
came "alive" as they never got full ownership of their resources or
could not reach their invariants. Similarly a moved from object are no
longer "alive" as they no longer own the resources and the invariants
might be broken.

2) Why is required that moved from objects be in a invariant unbroken
state?

Isn't the point of move constructor, to transfer ownership of an
internal resource and its invariant from one object to another? If we
requiring the source object to keep an invariant after the move. That
will mean that at the minimum some empty state re-construction has to
happen for the source. But not all object have an valid empty state.
Default construction might be an valid empty state. But that might
also be very expensive (boost::array). And not all objects have
default constructors anyway. Also requiring the moved object to keep
an invariant, means that there will have to be two valid object after
a move. One equal to the source before the copy, and another one in
some "valid" default or empty state. I think its better for copy to
copy objects, and for move to just move objects, and thereby
invalidating the source.

3) How can we requiring empty state for objects that don't have one?
If the user is to reuse the moved from object afterwards. Any invalid
empty state will mean the user has to do reinitialize it (by
assignment operator) to some valid value before using it. Why not make
just make it undefined behavior for a user to use a moved from object
without re-initializing?

A parallel would be how a user can copy construct one pointer from
another, but before reusing the source pointer for something else the
user has to reinitialize it. Or reusing a pointer after a delete.

Hope someone can answer these question of mine or correct me on any
misunderstandings.

Cheers, Patrik

Alf P. Steinbach /Usenet

unread,
Oct 31, 2010, 3:38:43 AM10/31/10
to
Patrik: I think this is a good idea that deserves good discussion. Disclaimer: I
have the flu so not thinking clearly, but still. So, could you please post this
as a new thread, not buried deep within an old one? Thanks, -Alf

* Patrik Kahari, on 31.10.2010 00:35:

Bo Persson

unread,
Oct 31, 2010, 4:21:26 AM10/31/10
to
Patrik Kahari wrote:
> Hi
>
> Looking at Scott Meyers example, reading the comments here. and
> thinking about move only objects or expensive to copy objects (like
> mutex wrappers, framework singletons, etc). The following occurred
> to me.
>
> Are not all these proposals workarounds for the fact that the
> compiler is calling the destructor for moved from (zombie) objects?
> And why do we need the destructor to be called for such objects? It
> seems to me all these problems would disappear if the destructor
> was not called for moved from objects. It also seems to me to be
> the correct thing, from an ownership point of a view. My reasoning
> below ..
>
> The following point are not obvious to me.

>
> 1) Why are destructor's called for moved from objects?
>

Destructors are called for all live objects when they go out of scope
(or are explicitly destroyed).

>
> 2) Why is required that moved from objects be in a invariant
> unbroken state?
>

The moved from object might be part of a larger object, like a
container. It is generally important that this larger object remains
consistent, even if parts of it are moved somewhere else.

>
> 3) How can we requiring empty state for objects that don't have one?

We don't. Moving is an opportunity for a possible optimization. If it
cannot be done properly, or turns out to be more expensive than
copying, then we don't have to use it. Just continue copying, like
before.

Bo Persson


Howard Hinnant

unread,
Oct 31, 2010, 11:27:07 AM10/31/10
to
On Oct 30, 6:35 pm, Patrik Kahari <patrik.kah...@gmail.com> wrote:

> 1) Why are destructor's called for moved from objects?

Because this is not "destructive move semantics". Destructive move
semantics was considered from the beginning. Here is where this
alternative was discussed in 2002:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#Alternative%20move%20designs

This discussion lays out some of the problems with the design, and
invites others to work on it. It can coexist with non-destructive
move semantics. Both forms are useful.

Non-destructive move semantics is most useful for sequence permutation
algorithms such as swap or sort. In such an algorithm, the algorithm
ends with the same number of resources it begins with. Just
everything has been *moved* around. Thus destructing every time you
move from something is counterproductive.

> 2) Why is required that moved from objects be in a invariant unbroken
> state?

So that the object can be reused or destructed.

> 3) How can we requiring empty state for objects that don't have one?

We can't. Not every type has to have a move constructor or move
assignment operator.

-Howard

0 new messages