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

H

Howard Hinnant

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
 
S

Scott Meyers

Howard said:
From N2855:

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?

Scott
 
H

Howard Hinnant

Also from N2855:


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
 
S

Scott Meyers

Howard said:
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.

Scott
 
H

Howard Hinnant

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


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
 
S

Scott Meyers

Howard said:
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.

Scott
 
A

Alf P. Steinbach /Usenet

* Alf P. Steinbach /Usenet, on 16.08.2010 05:42:
* Howard Hinnant, on 16.08.2010 01:02:

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

Joshua Maurice

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

Alf P. Steinbach /Usenet

* Joshua Maurice, on 16.08.2010 22:26:
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
 
J

Joshua Maurice

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

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

Alf P. Steinbach /Usenet

* Pete Becker, on 16.08.2010 23:29:
Sigh. Doug Gregor. You can look it up.

Thanks.

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


Cheers,

- Alf
 
B

Balog Pal

Scott Meyers said:
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.
 
A

Alf P. Steinbach /Usenet

* Balog Pal, on 17.08.2010 22:51:
Scott Meyers said:
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
 
S

SG

* Balog Pal, on 17.08.2010 22:51:


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
 
G

Gene Bushuyev

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
 
A

Alf P. Steinbach /Usenet

* SG, on 18.08.2010 04:37:
If you're referring to the replacement of

mutable bool isCacheValid;

with

// "move-aware smart bool"
mutable replace_on_move<bool,false> isCacheValid;

in Scott's example, then yes.


If you include "prohibit automatic generation of move ops" in your
list, I don't see what the problem is with classes that don't "have a
natural empty state".

I didn't mean to include "prohibit..." for the last para but I wrote it. Thanks.

As for reasonably restricting automatic move ops generation, how about
avoiding them if there is *any* user-declared special function? So, in
the presence of a user-declared copy/move assignemt operator OR
destructor OR copy/move ctor, none of the (remaining) move ops are
generated implicitly. That catches at least Scott's and your TicTacToe
example. And by Howards analysis -- that is, we only expect
destruction and assignment (if available) to work on "zombies" -- this
should be sufficient. I think that using the existence of user-
declared (non copy/move) constructors as a kind of "invariant
detector" (like you suggested) would be too restrictive and
unnecessarily disable move ops for most "aggregate-like" classes.

That's pretty smart! :)

It prohibits pull-the-rug-optimization for the cases where well-behaved C++0x
code might do things not anticipated by the C++98 code, namely, after an object
has been moved from,

* being copy assigned to (executes user operator=)
* being destroyed (executes user destructor)

One question I'm not entirely sure of, is

* being copied from via copy constructor (executes user copy constructor)

part of the list of things that well-behaved C++0x code might do with a
moved-from object? I think this list of well-defined operations should be
delineated by the standard. As I see it copying from is not part of that list,
because the object might just have a minimal invariant established, sufficient
to support destruction and nothing more.

And if being copied from is not part of that list of possible operations then
whatever a user-defined copy constructor does (e.g. setting a pointer member to
point inside the object itself) cannot affect an operation O that in
well-behaved code follow after a move-from invocation of an automatically
generated move op M, because O would have to be a user defined destructor or
user defined assignment, in which case there would be no M.

I.e., if my thinking here is correct, under the rules of prohibiting automatic
move op generation when there's user defined destructor or copy assignment,
there should be no need to also prohibit for user defined copy constructor?

Just to fill out the picture, dotting the i's, AFAICS your rules work also for a
class that just has a sub-object that has a user-defined assignment or
destruction, and so on recursively.

Also, crossing the t's, it would be "non-trivial" user defined destructor.

Because many people (including myself) define destructors for other purposes
than directly customizing destruction, e.g. to make a class polymorphic. Or just
to follow a common coding convention. This then yields a little problem when the
class definition only declares the destructor and it's defined as trivial in a
separately compiled file, so I think the wording would have to refer to the
class definition, that move ops are only generated automatically when it's
/known from the class definition only/ that no non-trivial destructor is defined
(i.e. the class definition has no user defined destructor or a destructor is
defined with empty body in the class definition).

Finally, regarding previous thinking about this, In N3053 Bjarne Stroustrup &
Lawrence Crawl wrote, as 1 point of 4, "Having unnecessarily different rules for
when to generate moves and copies would cause surprising behavior."

However, in this thread examples have been presented, by Scott Meyers and me,
that show that having the particular identical-for-copy-and-move rules that were
adopted in the N3090 draft standard, cause surprising behavior, namely incorrect
Undefined Behavior, by unexpectedly invalidating class invariants in C++98 code.
Thus, AISI the rule you propose is not "unnecessarily different", and not in
conflict in with N3090. It's very /necessarily/ different. :)


Cheers,

- Alf (hoping I'm using the word "trivial" in the right sense here)
 
A

Alf P. Steinbach /Usenet

* 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
 
S

SG

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
 
S

Scott Meyers

Gene said:
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
 
S

SG

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top