Vector :: push_back insists on using a copy constructor, although a move mechanism is provided

I was getting a weird error from gcc and can't figure out why. I made the following sample code to make the problem more clear. Basically, there is a certain class for which I make it a copy constructor and a copy assignment operator private to accidentally call them.

#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 does not work with gcc. In fact, gcc complains that "branch :: branch (const branch &) is private", which, as I understand it, should not be called.

The assignment operator works, since if I replaced the body of main () with

 branch a; branch b; b = a; 

It will compile and execute as expected.

Is this the correct gcc behavior? If so, what is wrong with the above code? Any suggestion is helpful to me. Thanks!

+14
source share
2 answers

Try adding "noexcept" to the move constructor declaration.

I cannot cite this standard, but recent versions of gcc require either the copy constructor to be publicly available or the move constructor to be declared "noexcept". Regardless of the "noexcept" qualifier, if you make a public copy constructor, it will behave as you expect at runtime.

+16
source

Unlike the previous answer, gcc 4.7 incorrectly rejected this code, an error that was fixed in gcc 4.8 .

Full standard behavior for vector<T>::push_back :

  • If there is only a copy constructor and a move constructor, push_back will copy its argument and provide a reliable security guarantee. That is, if push_back fails due to an exception caused by the redistribution of the vector storage, the original vector will remain unchanged and applicable. This is a well-known behavior from C ++ 98, and it is also the cause of the mess that follows.
  • If there is a noexcept move noexcept for T , push_back move out of its argument and provide a reliable exception guarantee. There are no surprises here.
  • If there is a move constructor that is not noexcept , and there is also a copy constructor, push_back will copy the object and provide a solid security guarantee. This is unexpected at first glance. Although push_back can move here, this would only be possible by sacrificing a reliable exception guarantee. If you ported code from C ++ 98 to C ++ 11, and your type is movable, that would quietly change the behavior of existing push_back calls. To avoid this error and maintain compatibility with C ++ 98 code, C ++ 11 reverts to a slower copy. This is gcc 4.7 behavior. But there is still ...
  • If there is a move constructor that is not noexcept but has no copy constructor at all, i.e. an element can be moved and not copied - push_back will follow this step, but will not give a strong exception guarantee. Here gcc 4.7 did wrong. In C ++ 98, there is no push_back for types that can move but not be copied. Therefore, sacrificing strong exception safety here does not violate existing code. That's why it is allowed, and the source code is actually legitimate C ++ 11.

See cppreference.com in push_back :

If an exception is selected, this function has no effect (strong exception).

If the move constructor T is not inconsequential, and the copy instance is not available, the vector will use the throw move constructor. If he throws, the warranty is void, and the effects are undefined.

Or a slightly more confusing Β§23.3.6.5 from C ++ 11 Standard (highlighted by me):

Causes redistribution if the new size is larger than the old. If redistribution does not occur, all iterators and links until the entry point remains valid. If an exception is thrown otherwise than by the copy constructor, move constructor, assignment operator, or move assignment operator T or any input operation, the input is not an effect. If an exception is thrown by the non-CopyInsertable T move constructor, no effects are defined.

Or, if you don’t like to read, Scott Meyer, leading the conversation in 2013 (beginning at 0:30:20 with an interesting part at about 0:42: 00).

+9
source

Source: https://habr.com/ru/post/1206367/


All Articles