Main Page | Report Page

 

  Computers Forum Index » Computer Languages (C++) » Class invariants and implicit move constructors...

Author Message
Alf P. Steinbach /Usenet...
Posted: Mon Aug 16, 2010 5:16 am
 
* Alf P. Steinbach /Usenet, on 16.08.2010 05:42:
Quote:
* 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)

--
blog at <url: http://alfps.wordpress.com>
 
Joshua Maurice...
Posted: Mon Aug 16, 2010 8:26 pm
 
On Aug 15, 6:04 pm, Howard Hinnant <howard.hinn... at (no spam) gmail.com> wrote:
Quote:
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.
 
Joshua Maurice...
Posted: Mon Aug 16, 2010 9:31 pm
 
On Aug 16, 2:21 pm, "Alf P. Steinbach /Usenet" <alf.p.steinbach
+use... at (no spam) gmail.com> wrote:
Quote:
* Joshua Maurice, on 16.08.2010 22:26:



On Aug 15, 6:04 pm, Howard Hinnant<howard.hinn... at (no spam) 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.

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.

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...
Posted: Tue Aug 17, 2010 1:21 am
 
* Joshua Maurice, on 16.08.2010 22:26:
Quote:
On Aug 15, 6:04 pm, Howard Hinnant<howard.hinn... at (no spam) 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.

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,

- Alf

--
blog at <url: http://alfps.wordpress.com>
 
Pete Becker...
Posted: Tue Aug 17, 2010 1:29 am
 
On 2010-08-16 17:21:28 -0400, Alf P. Steinbach /Usenet said:

Quote:

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)
 
Alf P. Steinbach /Usenet...
Posted: Tue Aug 17, 2010 1:31 am
 
* Pete Becker, on 16.08.2010 23:29:
Quote:
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... ;-)


Cheers,

- Alf

--
blog at <url: http://alfps.wordpress.com>
 
Alf P. Steinbach /Usenet...
Posted: Wed Aug 18, 2010 1:48 am
 
* Balog Pal, on 17.08.2010 22:51:
Quote:
"Scott Meyers" <NeverRead at (no spam) 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.,

- Alf

--
blog at <url: http://alfps.wordpress.com>
 
Scott Meyers...
Posted: Wed Aug 18, 2010 11:43 pm
 
Gene Bushuyev wrote:
Quote:
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).

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).
 
SG...
Posted: Sat Oct 23, 2010 9:27 pm
 
On 22 Okt., 18:34, Howard Hinnant wrote:
Quote:
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...
Posted: Sun Oct 24, 2010 3:43 am
 
On Oct 23, 5:27 pm, SG <s.gesem... at (no spam) gmail.com> wrote:
Quote:
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.

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
 
 
Page 2 of 2    Goto page Previous  1, 2
All times are GMT
The time now is Sat Jul 26, 2014 1:03 am