Re: Class invariants and implicit move constructors (C++0x)
This is a multi-part message in MIME format.
--------------000001080104070002000502
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit
* Howard Hinnant, on 16.08.2010 01:02:
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.
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...
--
blog at <url: http://alfps.wordpress.com>
--------------000001080104070002000502
Content-Type: text/html; charset=ISO-8859-1;
name="implicit_move_constructor.html"
Content-Transfer-Encoding: 8bit
Content-Disposition: attachment;
filename="implicit_move_constructor.html"
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Implicit move constructors considered harmful</title>
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5" />
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
<!-- link rel="stylesheet" href="main.css" type="text/css" /-->
<style type="text/css">
td { vertical-align: top; }
ins { background-color: #A0FFA0; }
del { background-color: #FFA0A0; }
</style>
</head>
<body>
<h1>
Implicit move constructors considered harmful</h1>
<p>
ISO/IEC JTC1 SC22 WG21 Nxxxx = xx-xxxx - 2010-08-xx
</p>
<p>
Alf P. Steinbach
</p>
<p>
<a href="#Problem">Problem</a><br/>
<a href="#PracticalConsiderations">Practical considerations</a><br/>
<a href="#PossibleSolutions">Possible solutions</a><br/>
<a href="#Proposal">Proposal in general</a><br/>
<a href="#Paragraphs">Paragraph updates for the proposal</a><br/>
<br/>
<a href="#Acknowledgements">Acknowledgments</a><br/>
<a href="#References">References</a>
</p>
<!------------------------------------------------------------------------->
<h2><a name="Introduction">Problem</a></h2>
<p>
In the <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3090.pdf">N3090</a>
draft of C++0x,
</p>
<blockquote>
<p>
?12.8/10 Copying and moving class objects [class.copy]:
</p>
<p>
“If the class definition does not explicitly declare a move constructor, one will
be implicitly declared as defaulted if and only if</p>
<ul>
<li>
X does not have a user-declared copy constructor and
</li>
<li>
the move constructor would not be implicitly defined as deleted.
</li>
</ul>
<p>
[<em>Note</em>: When the move constructor is not implicitly declared or explicitly
supplied, expressions that otherwise would have invoked the move constructor may
instead invoke a copy constructor. –<em>endnote</em>]”
</p>
</blockquote>
<p>
The relevant conditions for “defined as deleted” are laid out by ?12.8/12;
essentially a defaulted move constructor is defined as deleted if there are members that
cannot be moved, and in this case ?12.8/10 does not yield an automatically generated
move constructor</p>
<p>
Essentially the same rule holds for implicit generation of a move assignment operator,
specified by ?12.8/22.
</p>
<p>
A class that is defined in terms of
types like <code>std::vector</code> that handle copying automagically,
needs not have a user defined copy constructor. By ?12.8/10 it will then acquire an
automatically generated move constructor. And moving an object of such a class can then
easily break its class invariant, e.g.</p>
<blockquote><pre><code>
#include <vector>
#include <algorithm>
#include <utility> // std::move
//--------------------------- Old C++98 code:
enum PositionState { empty, nought, cross };
class SomeonesClass
{
private:
std::vector< PositionState > positions_;
public:
SomeonesClass(): positions_( 9 ) {}
SomeonesClass& operator=( SomeonesClass const& other )
{
for( int i = 0; i < 9; ++i )
{
positions_.at( i ) = other.positions_.at( i );
}
return *this;
}
#ifdef EMULATE_N3090
SomeonesClass( SomeonesClass&& other )
: positions_( std::move( other.positions_ ) )
{}
#endif
};
//--------------------------- New C+0x driver code:
template< class Type >
void fastSwap( Type& a, Type& b )
{
Type temp( std::move( a ) );
a = std::move( b );
b = std::move( temp );
}
int main()
{
SomeonesClass board1;
SomeonesClass board2;
fastSwap( board1, board2 );
}
</code></pre></blockquote>
</p>
<p>
The compilers I have, g++ 4.4.1 and MSVC 10.0, do not support N3090’s implicit
move constructor generation. But when compiling the above with <code>EMULATE_N3090</code>
defined, the resulting executable crashes, as expected. The assignment operator for the
class (in this case causing the problem, by assuming that the C++98 class invariant
holds) is utterly na?ve, but then so is much existing code…
</p>
<p>
In short, an implicitly generated move constructor can easily break C++98 code by
invalidating the class invariant, in particular for custom assignment operators.
</p>
<p>
And ditto for implicitly generated move assignment operators. They can easily break
C++98 code by invalidating class invariants. And then, in practice, for <em>any operation</em>,
since objects are likely to be used after being assigned from.</p>
<p>
The context here is using C++98
code from C++0x code where an assumption is made that move operations are “safe”.
For example as in <code>fastSwap</code> above. Without that safety assumption template code
that uses move operations would be impractical.
</p>
<p>
Scott Meyers has pointed out in
<a href="http://groups.google.com/group/comp.lang.c++/msg/a9203e708b64422f">a Usenet posting</a>
that simple class invariant checks in destructors
are susceptible to this problem. This then extends the problem to classes
whose instances have a natural empty state defined by emptiness of standard library
containers such as <code>std::vector</code> and <code>std::string</code>. Acknowledgment:
I was not aware of the general problem until reading Scott’s posting.
</p>
<!------------------------------------------------------------------------->
<h2><a name="PracticalConsiderations">Practical considerations</a></h2>
<p>
It’s unclear how much C++98 code would be broken be implicitly generated
move constructors and move assignment operators.</p>
<p>
Also, it’s highly desirable to get automatic move functionality for existing
C++98 classes.
</p>
<p>
However, <em>constraining the cases where move constructors are generated is not
necessarily in conflict with the efficiency goal for existing C++98 code</em>.
</p>
<p>
For example, when Microsoft’s Visual C++ compiler is invoked directly it
defaults to not supporting RTTI and not supporting proper stack unwinding for
exceptions. These language features can be turned on by options (which are set
by default by an IDE project). And for another example, the g++ compiler lets
the programmer specify “fast” floating point operation that do not
honor IEEE 754 rules for NaNs etc., while leaving <code>std::numeric_limits<double>::is_iec559</code>
as <code>true</code> (indicating IEEE 754 conformance), an optimization that like
Microsoft’s would break general standard C++ code, but presumably not the
code in question.
</p>
<p>
So in C++ programming there is a long-standing established practice for <em>letting
the programmer decide</em>, via tool usage, whether to apply optimizations that
would break general standard C++
code – but not the code in question.
</p>
<!------------------------------------------------------------------------->
<h2><a name="PossibleSolutions">Possible solutions</a></h2>
<p>
Given the practical considerations in the previous section, that constraining
the automatic generation of move constructors and move assignment operators is
not necessarily in conflict with the goal of efficiency improvement for
existing code, I see the following possible solutions:</p>
<ul>
<li>
Add an attribute <code>[movable]</code> that causes move constructor
and move assignment operator to be generated as per N3090 rules (or perhaps
modified rules), for this class, where move constructor and move assignment
operator are not generated for a class without this attributes.
<li>
Constrain the automatic generation of move constructors and of move
assignment operators, e.g. only generate if there is no user defined
constructor at all (since a user defined constructor indicates
establishment of a class invariant), and hope for the best.
</li>
<li>
Do nothing, assuming that very little C++98 code will be broken, and
again, hope for the best.
</li>
</li>
</ul>
</p>
The first two possible solutions are not mutually exclusive: the N3090 rules
can both be constrained a little, and made to apply only for a class with
<code>movable</code> attribute.</p>
<p>
A compiler vendor may still offer the programmer options to
have automatically generated move constructors and move assignment operators for
old code, as both the Microsoft and GNU compilers do today for other possibly
code-breaking optimizations.
</p>
<!------------------------------------------------------------------------->
<h2><a name="Proposal">Proposal in general: a [movable] attribute</a></h2>
<p>
To solve the problem of automatically generated move constructors and move assignment
operators breaking class invariants by N3090 rules, I propose introducing a
<strong><code>[movable]</code></strong> class attribute, such that move constructors and
move assignment operators are only generated automatically for a class with this attribute.
</p>
<p>
I believe that that will facilitate easy optimization of old C++98 code to take advantage
of C++0x move semantics, in a more safe way.
</p>
<p>
For wholesale optimization without code changes, the compiler vendor may offer
some option to treat the compiled classes <em>as if</em> they had <code>[movable]</code>
attribute.</p>
<p>
With such a compiler option, like existing compiler options, it’s then the
programmer’s active choice, choosing an optimization that may break general
standard C++ code – and not having the rules changed underfoot to break code.
</p>
<p>
With the <code>[movable]</code> attribute I see no point in constraining the automatic
generation rules from those in N3090, since the attribute is a very strong explicit
indicator of requirements on the class.</p>
<!------------------------------------------------------------------------->
<h2><a name="Paragraphs">Paragraph updates for the proposal</a></h2>
<!------------------------------------------------------------------------->
<h2><a name="Acknowledgments">Acknowledgments</a></h2>
<p>
xxxx
</p>
</body>
</html>
--------------000001080104070002000502--