Noexcept and copy, moving constructors

In any case, this seems to be consistent with the fact that the standard library should call copy constructors instead of moving constructors when the move constructor is no exception (false).

Now I do not understand why this is so. And even more, Visual Studio VC v140 and gcc v 4.9.2 seem to do it differently.

I do not understand why this is not a concern. vector. I mean, how should vector :: resize () be able to give a reliable guarantee of an exception if T is not. As I see it, the exception level for the vector will depend on T. Regardless of whether copying or moving is used. I understand that there is no exception for the compiler to optimize exception optimization.

This small program calls the copy constructor when compiling with gcc and moving the constructor when compiling with Visual Studio.

include <iostream> #include <vector> struct foo { foo() {} // foo( const foo & ) noexcept { std::cout << "copy\n"; } // foo( foo && ) noexcept { std::cout << "move\n"; } foo( const foo & ) { std::cout << "copy\n"; } foo( foo && ) { std::cout << "move\n"; } ~foo() noexcept {} }; int main() { std::vector< foo > v; for ( int i = 0; i < 3; ++i ) v.emplace_back(); } 
+4
source share
2 answers

This is a multifaceted question, so bear with me while I look at various aspects.

The standard library expects all user types to always provide a basic exception guarantee. This guarantee states that when an exception is thrown, the objects involved are still in a valid, if unknown, state that no resources are leaking, that no fundamental language invariants are violated, and that a frightening action has not occurred at a distance (the last one of them is not part of the formal definition, but this is actually an implied assumption).

Consider the copy constructor for the Foo class:

 Foo(const Foo& o); 

If this constructor throws, the basic exception guarantee gives you the following knowledge:

  • New object not created. If the constructor throws, an object is not created.
  • o not been changed. His only participation here is a reference to const, so it cannot be changed. Other cases fall under the heading "creepy action at a distance" or perhaps the "fundamental language invariant."
  • No resources were missed, the program as a whole is still agreed.

In the move constructor:

 Foo(Foo&& o); 

basic warranty gives fewer warranties. o can be changed, because it is involved through a non-constant link, so it can be in any state.

Then view vector::resize . Its implementation, as a rule, will follow the same pattern:

 void vector<T, A>::resize(std::size_t newSize) { if (newSize == size()) return; if (newSize < size()) makeSmaller(newSize); else if (newSize <= capacity()) makeBiggerSimple(newSize); else makeBiggerComplicated(newSize); } void vector<T, A>::makeBiggerComplicated(std::size_t newSize) { auto newMemory = allocateNewMemory(newSize); constructAdditionalElements(newMemory, size(), newSize); transferExistingElements(newMemory); replaceInternalBuffer(newMemory, newSize); } 

The key function here is transferExistingElements . If we use only copying, it has a simple guarantee: it cannot change the original buffer. Therefore, if an operation is called at some point, we can simply destroy the newly created objects (keep in mind that the standard library absolutely cannot work with throwing destructors), discard a new buffer and reconstruct. The vector will look as if it had never been modified. This means that we have a strong guarantee, although the element instance constructor offers only a weak guarantee.

But if we use movement instead, this will not work. After moving one object, any subsequent exception means that the original buffer has been changed. And since we have no guarantee that moving objects will not rush back either, we cannot even recover. Thus, in order to maintain a strong guarantee, we must demand that the move operation not make any exceptions. If we have this, we are fine. And so we have move_if_noexcept .

Regarding the difference between MSVC and GCC: MSVC only supports noexcept from version 14, and since this is still under development, I suspect that the standard library has not been updated to take advantage.

+10
source

The main problem is that it is not possible to provide reliable protection for exceptions using the metadata move constructor. Imagine if in a vector resize halfway through moving elements to a new buffer, a movement constructor is created. How could you restore the previous state? You cannot use the move constructor again, because, well, it can just go on.

Copying works to provide a reliable guarantee of safety no matter what nature casts, because the original state is not damaged, so if you cannot build a completely new state, you can simply clear the partially constructed state, and then you because the old state is still waiting for you here. Moving designers does not offer this security system.

In principle, it is impossible to offer a categorically exclusive resizing () with throwing movement, but easy with throwing. This fundamental fact is reflected throughout the standard library.

GCC and VS view this differently because they are at different stages of compliance. VS left noexcept one of the last functions that they implement, so their behavior is a kind of intermediate behavior between C ++ 03 and C ++ 11/14. In particular, since they have no way to determine if your move constructor is noexcept or not, they just have to guess. From memory, they simply assume that it is noexcept , because throwing move constructors is not common, and inability to move will be a critical issue.

+9
source

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


All Articles