Adrian
Adrian

Reputation: 10911

Why does std::vector allow the use of a throwable move constructor for the type it contains?

Apparently, std::move_if_noexcept() will call the move constructor, even if it is not marked as noexcept if there is no copy constructor available.

From cpprefeerence.com (emphasis mine):

Notes

This is used, for example, by std::vector::resize, which may have to allocate new storage and then move or copy elements from old storage to new storage. If an exception occurs during this operation, std::vector::resize undoes everything it did to this point, which is only possible if std::move_if_noexcept was used to decide whether to use move construction or copy construction. (unless copy constructor is not available, in which case move constructor is used either way and the strong exception guarantee may be waived)

As std::vector is using this function on reallocation, that could leave the vector and possibly the application in an undetermined state. So, why would this be allowed?

Upvotes: 2

Views: 631

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 473946

Let's say you're doing what vector is doing when it would use move_if_noexcept. That is, you have some object obj, and you need to construct a new value of that type from obj. And after that, you're going to delete obj. That's a prime case for moving the object, so vector does that where possible.

If movement is noexcept, then moving obj is exception safe by definition. If it isn't noexcept, then you need to ask: what happens if the move constructor throws? What is the state of obj in that case? The answer is... you don't know. Even worse, what about the state from any objects you already moved from successfully? Can you move them back?

When it comes to copy constructors however, you do know. A copy constructor takes a const& to the source object. So by definition, a failed copy operation cannot modify obj (and yes, we know you can const_cast, but that makes your copy constructor a lie. Lies like that are why auto_ptr doesn't exist anymore, and I would guess that there's a blanket prohibition in the standard on lying copy constructors). So on a failed copy, obj is in its original state.

Therefore, if movement can throw, copying is preferred, as this provides the strong exception guarantee: in the event of an exception, everything goes back to the way it was.

However, if your only option is a throwing move, then you have two choices: make it so that such a type cannot ever be used with vector, or offer whatever exception guarantee the type itself offers on movement failure. And the latter is what is chosen.

It's allowed because that's what you asked for. Your choice of type doesn't allow for the strong exception guarantee, so it cannot be provided. But you can still make a vector of such types; you just need to deal with the possibility of non-copyable movement failure.

And since you're the kind of person who uses types that cannot provide a strong exception guarantee, you clearly must know how to handle these scenarios, right?

Upvotes: 4

Related Questions