Section: 23.2.2 [container.requirements.general] Status: Resolved Submitter: Rani Sharoni Opened: 2009-02-12 Last modified: 2016-01-28
Priority: Not Prioritized
View other active issues in [container.requirements.general].
View all other issues in [container.requirements.general].
View all issues with Resolved status.
Discussion:
Introduction
This proposal is meant to resolve potential regression of the N2800 draft, see next section, and to relax the requirements for containers of types with throwing move constructors.
The basic problem is that some containers operations, like push_back
,
have a strong exception safety
guarantee (i.e. no side effects upon exception) that are not achievable when
throwing move constructors are used since there is no way to guarantee revert
after partial move. For such operations the implementation can at most provide
the basic guarantee (i.e. valid but unpredictable) as it does with multi
copying operations (e.g. range insert).
For example, vector<T>::push_back()
(where T
has a move
constructor) might resize the vector
and move the objects to the new underlying
buffer. If move constructor throws it might
not be possible to recover the throwing object or to move the old objects back to
the original buffer.
The current draft is explicit by disallowing throwing move
for some operations (e.g. vector<>::reserve
) and not clear about other
operations mentioned in 23.2.2 [container.requirements.general]/10
(e.g. single element insert
): it guarantees strong exception
safety without explicitly disallowing a throwing move constructor.
Regression
This section only refers to cases in which the contained object is by itself a standard container.
Move constructors of standard containers are allowed to throw and therefore existing operations are broken, compared with C++03, due to move optimization. (In fact existing implementations like Dinkumware are actually throwing).
For example, vector< list<int> >::reserve
yields
undefined behavior since list<int>
's move constructor is allowed to throw.
On the other hand, the same operation has strong exception safety guarantee in
C++03.
There are few options to solve this regression:
Option 1 is suggested by proposal N2815 but it might not be applicable for existing implementations for which containers default constructors are throwing.
Option 2 limits the usage significantly and it's error prone
by allowing zombie objects that are nothing but destructible (e.g. no clear()
is allowed after move). It also potentially complicates the implementation by
introducing special state.
Option 3 is possible, for example, using default
construction and swap
instead of move for standard containers case. The
implementation is also free to provide special hidden operation for non
throwing move without forcing the user the cope with the limitation of option-2
when using the public move.
Option 4 impact the efficiency in all use cases due to rare throwing move.
The proposed wording will imply option 1 or 3 though option 2 is also achievable using more wording. I personally oppose to option 2 that has impact on usability.
Relaxation for user types
Disallowing throwing move constructors in general seems very restrictive
since, for example, common implementation of move will be default construction
+ swap
so move will throw if the
default constructor will throw. This is currently the case with the Dinkumware
implementation of node based containers (e.g. std::list
)
though this section doesn't refer to standard types.
For throwing move constructors it seem that the implementation should have no problems to provide the basic guarantee instead of the strong one. It's better to allow throwing move constructors with basic guarantee than to disallow it silently (compile and run), via undefined behavior.
There might still be cases in which the relaxation will break existing generic code that assumes the strong guarantee but it's broken either way given a throwing move constructor since this is not a preserving optimization.
[ Batavia (2009-05): ]
Bjarne comments (referring to his draft paper): "I believe that my suggestion simply solves that. Thus, we don't need a throwing move."
Move to Open and recommend it be deferred until after the next Committee Draft is issued.
[ 2009-10 Santa Cruz: ]
Should wait to get direction from Dave/Rani (N2983).
[ 2010-03-28 Daniel updated wording to sync with N3092. ]
The suggested change of 23.3.5.4 [deque.modifiers]/2 should be removed, because the current wording does say more general things:
2 Remarks: If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of
T
there are no effects. If an exception is thrown by the move constructor of a non-CopyConstructibleT
, the effects are unspecified.The suggested change of 23.3.11.3 [vector.capacity]/2 should be removed, because the current wording does say more general things:
2 Effects: A directive that informs a
vector
of a planned change in size, so that it can manage the storage allocation accordingly. Afterreserve()
,capacity()
is greater or equal to the argument ofreserve
if reallocation happens; and equal to the previous value ofcapacity()
otherwise. Reallocation happens at this point if and only if the current capacity is less than the argument ofreserve()
. If an exception is thrown other than by the move constructor of a non-CopyConstructible
type, there are no effects.
[2011-03-15: Daniel updates wording to sync with N3242 and comments]
The issue has nearly been resolved by previous changes to the working paper, in particular all suggested changes for
deque
andvector
are no longer necessary. The still remaining parts involve the unordered associative containers.
[2011-03-24 Madrid meeting]
It looks like this issue has been resolved already by noexcept
paper N3050
Rationale:
Resolved by N3050
Proposed resolution:
23.2.2 [container.requirements.general] paragraph 10 add footnote:
-10- Unless otherwise specified (see 23.2.7.2 [associative.reqmts.except], 23.2.8.2 [unord.req.except], 23.3.5.4 [deque.modifiers], and 23.3.11.5 [vector.modifiers]) all container types defined in this Clause meet the following additional requirements:
- …
[Note: for compatibility with C++ 2003, when "no effect" is required, standard containers should not use the
value_type
's throwing move constructor when the contained object is by itself a standard container. — end note]
23.2.8.2 [unord.req.except] change paragraph 2+4 to say:
-2- For unordered associative containers, if an exception is thrown by any operation other than the container's hash function from within an
[…] -4- For unordered associative containers, if an exception is thrown from within ainsert()
function inserting a single element, theinsert()
function has no effect unless the exception is thrown by the contained object move constructor.rehash()
function other than by the container's hash function or comparison function, therehash()
function has no effect unless the exception is thrown by the contained object move constructor.
Keep 23.3.5.4 [deque.modifiers] paragraph 2 unchanged [Drafting note: The originally proposed wording did suggest to add a last sentence as follows:
If an exception is thrown by
push_back()
oremplace_back()
function, that function has no effects unless the exception is thrown by the move constructor ofT
.
— end drafting note ]
-2- Remarks: If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of
T
there are no effects. If an exception is thrown by the move constructor of a non-CopyInsertableT
, the effects are unspecified.
Keep 23.3.11.3 [vector.capacity] paragraph 2 unchanged [Drafting note: The originally proposed wording did suggest to change the last sentence as follows:
If an exception is thrown, there are no effects unless the exception is thrown by the contained object move constructor.
— end drafting note ]
-2- Effects: A directive that informs a
vector
of a planned change in size, so that it can manage the storage allocation accordingly. Afterreserve()
,capacity()
is greater or equal to the argument ofreserve
if reallocation happens; and equal to the previous value ofcapacity()
otherwise. Reallocation happens at this point if and only if the current capacity is less than the argument ofreserve()
. If an exception is thrown other than by the move constructor of a non-CopyInsertable
type, there are no effects.
Keep 23.3.11.3 [vector.capacity] paragraph 12 unchanged [Drafting note: The originally proposed wording did suggest to change the old paragraph as follows:
-12- Requires:
IfIf an exception is thrown, there are no effects unless the exception is thrown by the contained object move constructor.value_type
has a move constructor, that constructor shall not throw any exceptions.
— end drafting note ]
-12- Requires: If an exception is thrown other than by the move constructor of a non-
CopyInsertable
T
there are no effects.
Keep 23.3.11.5 [vector.modifiers] paragraph 1 unchanged [Drafting note: The originally proposed wording did suggest to change the old paragraph as follows:
-1-
Requires: IfRemarks: If an exception is thrown byvalue_type
has a move constructor, that constructor shall not throw any exceptions.push_back()
oremplace_back()
function, that function has no effect unless the exception is thrown by the move constructor ofT
.
— end drafting note ]
-1- Remarks: 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 anyInputIterator
operation there are no effects. If an exception is thrown by the move constructor of a non-CopyInsertable
T
, the effects are unspecified.