Matthieu N.
Matthieu N.

Reputation:

C++ Move semantics and Exceptions

In the forthcoming C++0x standard, what happens when an exception is thrown within/during the move constructor?

Will the original object remain? or are both the original and move-to object in an undefined state? what are the guarantees afforded by the language?

Upvotes: 27

Views: 7159

Answers (3)

Matthieu M.
Matthieu M.

Reputation: 299760

Your question then amounts to a question about Exception Guarantees. There are 3 types of Exceptions Guarantees (that apply to functions):

  • No exception guarantee at all (not really a type... but it may happen if no concern is given to the matter)
  • Basic Exception Guarantee: Technically correct, but not Functionally correct (ie no resource is leaked, the program will terminate without an abrupt halt, but it may have undesired side-effects, like the payment being cashed-in but the command not being registered)
  • Strong Exception Guarantee: All or Nothing (like a transaction), that is either everything is done right, or we rollback to the previous state.
  • No Throw Exception Guarantee: This does not throw, ever, so no worry.

When you compose your function, you usually pick up existing functions with their own guarantees. It is difficult to increase the exception guarantee, that is your are generally limited by the weakest guarantee used.

W.r.t your question, it takes at least a Strong Exception Guarantee for the original object to be left untouched if an exception is thrown.

So, what happens if an exception is thrown during move-construction ? It depends on the guarantees exhibited by the sub-objects and the way you combined the calls...

  1. If an exception is thrown from a constructor, the object is not being built and all built subobjects are destroyed, in reverse order. This rule is also applicable to a move-constructor
  2. Unless you "wrap" the constructor in a try catch and somehow restore the objects that have been moved, they'll have lost their resources. Note that they must still be in a destructable state anyway, so technically the program will be correct.

In terms of exceptions guarantees, it means that by default, if all of the subobjects' constructors at least meet the Basic Exception Guarantee, then your move constructor will too, without any special care.

However, even if all the subobjects' constructors meet the Strong Exception Guarantee, it's unlikely you'll succeed in having your own move constructor meet it: this is the same issue that chaining transactions does not yield a transaction.

If only one of the subobjects' constructors may throw, and it meets the Strong Exception Guarantee, then your move constructor will naturally meet it itself if you initialize the throwing object first.

Hope this helped... exceptions are a wild beast to tame :)

Upvotes: 3

Michael Burr
Michael Burr

Reputation: 340188

I believe that the standards committee originally attempted to make it so move constructors would not be allowed to throw exceptions, but (at least as of today) found that trying to enforce that had too many pitfalls.

Proposal N3050, "Allowing Move Constructors to Throw (Rev 1)", has been incorporated into the draft standard. Essentially the proposal adds the capability for move constructors to throw, but to disallow 'throwing' moves to be used for certain operations where strong exception safety guarantees were needed (the library will fall back to copying the object if a non-throwing move isn't available).

If you mark a move constructor as non-throwing (noexcept) and an exception is thrown, std::terminate() will be called.

It might also be worth reading a blog article by David Abrahams that discusses the issues that N3050 was intended to address:

Upvotes: 12

Fred Nurk
Fred Nurk

Reputation: 14212

Depends on the type being moved from. It's possible to explicitly throw an exception, of course, from a move ctor, but also to implicitly call a copy ctor for a subobject from a move ctor. That copy ctor might do something that may throw, such as allocate memory. So, for the source object, the minimum guarantee is the original value may or may not remain, but it should still be in a destructable state.

For the object being moved to, it's the same as throwing from a ctor in current C++: destroy any constructed bases and members, execute the ctor's function try handler, if any, then propagate the exception. Details are in N3225 §15.2p2.

In particular, note that containers require their allocator types not have throwing move ctors:

Such move construction of the allocator shall not exit via an exception. [N3225 §23.2p8]

This allows the containers to move allocators around and use those allocators to clean up their items in the case of an exception when moving or copying items.

Upvotes: 3

Related Questions