Re: Implicit move constructor rules in c++0x still badly broken?
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3203.htm
If the definition of a class X 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,
* X does not have a user-declared copy assignment operator,
* X does not have a user-declared move assignment operator,
* X does not have a user-declared destructor, and
* the move constructor would not be implicitly defined as deleted.
this example *still* breaks.
Note again: I'm not saying implicit-move-ctors should necessarily be
removed completely, I'm just saying that Dave *does* have a point.
cheers,
Martin
- - -
Code Example Tweak#2 from David's post @http://cpp-next.com/archive/2010/10/implicit-move-must-go/
- - -
#define _GLIBCXX_DEBUG
#include <iostream>
#include <vector>
// An always-initialized wrapper for unsigned int
struct Number
{
Number(unsigned x = 0) : value(x) {}
operator unsigned() const { return value; }
private:
unsigned value;
};
struct Y
{
// Invariant: length == values.size(). Default ctor is fine.
// Maintains the invariant
void resize(unsigned n)
{
std::vector<int> s(n);
swap(s,values);
length = Number(n);
}
bool operator==(Y const& rhs) const
{
return this->values == rhs.values;
}
friend std::ostream& operator<<(std::ostream& s, Y const& a)
{
for (unsigned i = 0; i < a.length; ++i)
std::cout << a.values[i] << " ";
return s;
};
private:
std::vector<int> values;
Number length;
};
int main()
{
std::vector<Y> z(1, Y());
Y a;
a.resize(2);
z.push_back(a);
std::remove(z.begin(), z.end(), Y());
std::cout << z[1] << std::endl;};
I tweaked your tweaked example to simplify it a bit. The Number class
is not necessary to show the problem. Important to note is that this
example is not about an "implicit move constructor" breaking Z's
invariants but about a "implicitly generated move assignment operator"
doing so. std::remove after all move assigns, not move constructs.
VC10, that is the compiler I'm testing on, does not actually do
"implicitly generated move assignment operator" as specified by the
standard so I had to tweak your example (and mine) to get it to break
according to the standard. The relevant rules are:
If the definition of a class X 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,
- X does not have a user-declared move constructor,
- X does not have a user-declared copy assignment operator is not user-
declared
- X does not have a user-declared destructor, and
- The move assignment operator would not be implicitly defined as
deleted.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3203.htm
<code>
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <assert.h>
struct Z {
Z() : len_(0) {
assert(len_ == vec_.size()); // invariant len_ == vec_.size()
established
}
/*VC10 does not implicitly generate this move assignment operator as
specified by standard so I had to explicitly simulated it.*/
Z& operator=(Z&& rhs) {
vec_=std::move(rhs.vec_);
len_=std::move(rhs.len_); //invariant broken. rhs.len_ !=
rhs.vec_.size()
return *this;
}
void add(char ch) {
vec_.push_back(ch);
++len_; //invariant len_ == vec_.size() maintained
}
friend std::ostream& operator<<(std::ostream& os, const Z& z);
friend bool operator==(const Z& lhs, const Z& rhs);
private:
unsigned int len_;
std::vector<char> vec_;
};
bool operator==(const Z& lhs, const Z& rhs) {
return lhs.vec_ == rhs.vec_;
}
std::ostream& operator<<(std::ostream& os, const Z& z) {
assert(z.len_ == z.vec_.size()); //invariant will be broken for Z's
moved from. z.len_ != z.vec_.size()
for(unsigned int i = 0; i < z.len_; ++i) {
os << z.vec_.at(i);
}
return os;
}
int main() {
Z za;
za.add('a');
za.add('a');
Z zb;
zb.add('b');
zb.add('b');
std::vector<Z> vec;
vec.push_back(za);
vec.push_back(zb);
vec.push_back(za);
std::remove(vec.begin(), vec.end(), zb); //invariants will break for
(objects equal to) zb
copy(vec.begin(), vec.end(), std::ostream_iterator<Z>(std::cout, "
")); //assert will fail.
}
</code>
Thinking about it more I think Sancho is right. That this example
breaks does not worry me so much, as these elements at the end have
always been in a bit of a twilight zone. I cant see their state being
used for other things than logging and debugging (as they will almost
always be destroyed or assigned too). Maybe someone else can see such
use-cases, or find examples that rely on other function that
std::remove. My worry is still with the other types of examples, the
examples that break because of implicit move return (Like the
InvariantChecker). But I would be happy if someone came up with a
clear rule of thumb to avoid such designs.
Thanks, Patrik
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]