Reputation: 10519
I read this article cppnext implicit move but I didn't understand this problem:
#include <iostream>
#include <vector>
struct X
{
// invariant: v.size() == 5
X() : v(5) {}
~X()
{
std::cout << v[0] << std::endl;
}
private:
std::vector<int> v;
};
int main()
{
std::vector<X> y;
y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}
Under MSVC2010 there is no bug at runtime... Can anyone help me?
In this article there is this sentence :
The key problem here is that in C++03, X had an invariant that its v member always had 5 elements. X::~X() counted on that invariant, but the newly-introduced move constructor moved from v, thereby setting its length to zero.
I don't see why because we try to move X, v
length would be zero
Upvotes: 0
Views: 194
Reputation: 147036
MSVC2010 does not implement auto=generated move constructors, so even if your code would have produced one, you won't see it there. Thus, as always, "random implementation X did Y" is a pretty meaningless statement to make.
Upvotes: 1
Reputation: 227578
The problem the article is trying to illustrate is that when an X
is moved in the push_back, the invariant is broken. The temporary X
's vector v
will be empty after moving, so the destructor call will invoke undefined behaviour. It could be that you see no problem because your compiler is not implementing move semantics, or because the undefined behaviour does not result in any perceptible runtime error through pure chance.
You can check this behaviour with this simple program, where we explicitly move an X
instance:
#include <vector>
#include <iostream>
struct X {
X() : v(5) {}
std::vector<int> v;
};
int main() {
X x0;
std::cout << x0.v.size() << ", ";
X x1 = std::move(x0);
std::cout << x0.v.size() << "\n";
}
On GCC 4.7, these produce
5, 0
This comes from std::vector
's move constructor, which is used by X
's implicitly generated one. You can check std::vector
directly:
int main() {
std::vector<int> v0(5);
std::cout << v0.size() << ", ";
std::vector<int> v1 = std::move(v0);
std::cout << v0.size() << "\n";
}
This produces the same output.
Now, in the example you quote, the presence of a user defined destructor means that there is no implicitly generated move constructor, so X
is not moved in the push_back
.
Upvotes: 1
Reputation: 279395
I don't see why because we try to move X, v length would be zero
Well, first of all we don't try to move the temporary X()
in the push_back call. We do move it, or we don't[*].
If we were to move it (using an implicitly-generated move constructor), its data member v
is moved. The assumption of the article is that moving from a vector leaves it empty, although I don't remember whether that's required or just typical. But it certainly does leave v
in a state where it isn't guaranteed that you can access its former elements.
Remember, the whole point of moving is that it transfers expensive-to-copy resources from the source. So the temporary object is no longer guaranteed to have its internal array of 5 elements, because the destination of the move has taken it.
As to why it works in MSVC 2010 -- remember this code is presented in the article as an example just above "tweak #1 -- destructors suppress implicit move". C++11 does in fact contain this tweak -- no implicit move constructor is generated if there's a user-defined destructor (12.8/9). So the temporary is not moved, it's copied.
[*] Move or move not, there is no try. It's not up to the implementation whether or not to move it, the standard defines this.
Upvotes: 1
Reputation: 3081
In VS2010, the temporary will be be copied and then later destroyed. With move semantics, the temp object will simply be used for the vector.
Upvotes: 0