I was receiving a strange error from gcc and cannot figure out why. I made the following example code to make the problem more clear. Basically, there is a class defined, for which I make its copy constructor and copy assignment operator private, to prevent calling them accidentally.
#include <vector>
#include <cstdio>
using std::vector;
class branch
{
public:
int th;
private:
branch( const branch& other );
const branch& operator=( const branch& other );
public:
branch() : th(0) {}
branch( branch&& other )
{
printf( "called! other.th=%d\n", other.th );
}
const branch& operator=( branch&& other )
{
printf( "called! other.th=%d\n", other.th );
return (*this);
}
};
int main()
{
vector<branch> v;
branch a;
v.push_back( std::move(a) );
return 0;
}
I expect this code to compile, but it fails with gcc. Actually gcc complains that "branch::branch(const branch&) is private", which as I understand shouldn't be called.
The assignment operator works, since if I replace the body of main() with
branch a;
branch b;
b = a;
It will compile and run as expected.
Is this a correct behavior of gcc? If so, what's wrong with the above code? Any suggestion is helpful to me. Thank you!
Yes, std::vector<T>::push_back() creates a copy of the argument and stores it in the vector.
No. It doesn't call the move constructor. To call move constructor of element you will have to call std::move while pushing to vector itself.
The default copy constructor will copy all members – i.e. call their respective copy constructors. So yes, a std::vector (being nothing special as far as C++ is concerned) will be duly copied.
Try adding "noexcept" to the declaration of the move constructor.
I can't quote the standard, but recent versions of gcc appear to require either that the copy constructor be public or that the move constructor be declared "noexcept". Regardless of the "noexcept" qualifier, if you make the copy constructor public, it will behave as you expect at run-time.
Unlike suggested by the previous answer, gcc 4.7 was wrong to reject this code, a mistake which has been corrected in gcc 4.8.
The full standard-conforming behavior for vector<T>::push_back
is:
push_back
will copy its argument and will give the strong exception safety guarantee. That is, if the push_back fails due to an exception triggered by reallocation of the vector storage, the original vector will remain unchanged and usable. This is the known behavior from C++98 and it is also the reason for the mess that follows.noexcept
move constructor for T
, push_back
will move from its argument and will give the strong exception guarantee. No surprises here.noexcept
and there is also a copy constructor, push_back
will copy the object and give the strong exception safety guarantee. This is unexpected at first glance. While push_back
could move here, that would only be possible at the expense of sacrificing the strong exception guarantee. If you ported code from C++98 to C++11 and your type is movable, that would silently change the behavior of existing push_back
calls. To avoid this pitfall and maintain compatibility with C++98 code, C++11 falls back to the slower copy. This is what the gcc 4.7 behavior is all about. But there is more...noexcept
but no copy constructor at all - that is, the element can only be moved and not copied - push_back
will perform the move but will not give the strong exception safety guarantee. This is where gcc 4.7 went wrong. In C++98 there are no push_back
s for types that are movable but not copyable. So sacrificing the strong exception safety here does not break existing code. This is why it is allowed and the original code is in fact legal C++11.See cppreference.com on push_back
:
If an exception is thrown, this function has no effect (strong exception guarantee).
If T's move constructor is not noexcept and the copy constructor is not accessible, vector will use the throwing move constructor. If it throws, the guarantee is waived and the effects are unspecified.
Or a little more convoluted §23.3.6.5 from the C++11 Standard (emphasis added by me):
Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid. If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T or by any InputIterator operation there are no effects. If an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.
Or if you don't like reading, Scott Meyer's Going Native 2013 talk (starting at 0:30:20 with the interesting part at about 0:42:00).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With