The effects of failure in vector.push_back()

525 views
Skip to first unread message

Andrzej Krzemieński

unread,
Jan 15, 2014, 10:07:35 AM1/15/14
to std-dis...@isocpp.org
Hi,
I would like to determine what the Standard says about the effects of vector.push_back() ending in throwing an exception. (I do not count the situation where T's move constructor throws.) My intuition tells me that the size of the vector and the value of its elements remain unchanged, but its capacity might have changed. But my reading of the standard tells me something very different:

23.2.1 [container.requirements.general] /10:

Unless otherwise specified (see [...] 23.3.6.5) all container types defined in this Clause meet the following additional requirements: [...] if an exception is thrown by a push_back() or push_front() function, that function has no effects.

This would say that both size and capacity remain unchanged, but "unless specified otherwise" appears to take precedence, and cancel the above requirement, and 23.3.6.5 says this:

23.3.6.5 [vector.modifiers] /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 any InputIterator operation there are no effects. If an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.

This in turn means that if the copy ctor/assignment throws anything can happen. The effects are unspecified. I get that from the "other than" part.

Is my reading correct?

Apart from that, what is the intention, should vector's capacity remain unchanged after any exception is thrown (except T's move)?

Regards,
&rzej

Howard Hinnant

unread,
Jan 15, 2014, 12:08:30 PM1/15/14
to std-dis...@isocpp.org
I can speak to the intention:

vector<T>::push_back(const T&);
vector<T>::push_back(T&&);

For types T that are CopyConstructible, and for types T that are noexcept MoveConstructible (whether or not they are CopyConstructible), the push_back should have the strong exception safety guarantee for all observable characteristics, including capacity, no matter whether the exception is thrown by buffer reallocation, or by a copy construction (there are no assignment operations done, and move construction can not throw in this case).

For types T that are not CopyConstructible, and have a throwing move construction, the push_back should have the basic exception safety guarantee. If an exception is thrown, the vector is left in a valid, but unspecified state. As a practical matter, I would expect such a state to be one of the following:

1. If buffer allocation throws, I would expect the vector to remain unchanged, including capacity.

2. If buffer allocation is successful, and the construction of the new element throws, I would expect the vector to remain unchanged, including capacity.

3. If buffer allocation is successful, and the construction of one of the existing elements throws (in moving from the old buffer to the new), I would expect vector capacity and size to remain unchanged, and for some of the vector's elements to be in a moved-from state.

4. If buffer allocation is unneeded, and the construction of the new element throws, I would expect the vector to remain unchanged, including capacity.

In summary, I see no reason for capacity or size to change if an exception is thrown for any reason at all. But I agree that is not what the standard says.

Howard

Stephan Tolksdorf

unread,
Jan 15, 2014, 2:42:55 PM1/15/14
to std-dis...@isocpp.org
On 2014-01-14 18:08, Howard Hinnant wrote:
> In summary, I see no reason for capacity or size to change if an exception is thrown for any reason at all. But I agree that is not what the standard says.

If the capacity does not have to remain unchanged when an exception is
thrown, the implementation of push_back could for certain types grow the
allocated buffer using realloc instead of allocating a new buffer and
freeing the old one. This can be an optimization.

- Stephan

Nevin Liber

unread,
Jan 15, 2014, 2:44:12 PM1/15/14
to std-dis...@isocpp.org
On 15 January 2014 13:42, Stephan Tolksdorf <s...@quanttec.com> wrote:
If the capacity does not have to remain unchanged when an exception is thrown, the implementation of push_back could for certain types grow the allocated buffer using realloc instead of allocating a new buffer and freeing the old one. This can be an optimization.

Given the current allocator interface, how can this be done?
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Stephan Tolksdorf

unread,
Jan 15, 2014, 3:10:09 PM1/15/14
to std-dis...@isocpp.org

On 2014-01-15 20:44, Nevin Liber wrote:
> On 15 January 2014 13:42, Stephan Tolksdorf <s...@quanttec.com
> <mailto:s...@quanttec.com>> wrote:
>
> If the capacity does not have to remain unchanged when an exception
> is thrown, the implementation of push_back could for certain types
> grow the allocated buffer using realloc instead of allocating a new
> buffer and freeing the old one. This can be an optimization.
>
>
> Given the current allocator interface, how can this be done?

Doesn't the "as-if" rule in principle allow the implementation to use
realloc for vectors constructed with the default allocator (assuming the
implementation knows that the default global operator new is not replaced).

- Stephan


Diggory Blake

unread,
Jan 15, 2014, 3:56:15 PM1/15/14
to std-dis...@isocpp.org
Only if the copy constructor of T is default implemented: it's not valid to move a struct/class instance in memory without using its copy or move constructor, and both the copy and move constructor require the original, unmoved instance to be passed in, which precludes the use of realloc.





--

--- You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Stephan Tolksdorf

unread,
Jan 15, 2014, 5:35:05 PM1/15/14
to std-dis...@isocpp.org
On 2014-15-01 21:56, Diggory Blake wrote:
> Only if the copy constructor of T is default implemented: it's not valid
> to move a struct/class instance in memory without using its copy or move
> constructor, and both the copy and move constructor require the
> original, unmoved instance to be passed in, which precludes the use of
> realloc.

Facebook's Folly library uses a custom type trait "IsRelocatable" to
identify types that are safely "movable" in memory without calling the
move constructor. Even if such a type trait is not exposed to the user,
it could still be internally used for types provided by the library, e.g
string or vector<string> etc.

I don't want to argue here that the slightly improved optimization
opportunities or increased implementation flexibility justify the
weakening of the exception guarantee. I just wanted to point out a
possible motivation for not requiring the capacity to remain unchanged
when an exception is thrown.

- Stephan

> On Wed, Jan 15, 2014 at 8:10 PM, Stephan Tolksdorf <s...@quanttec.com
> <mailto:s...@quanttec.com>> wrote:
>
>
> On 2014-01-15 20:44, Nevin Liber wrote:
>
> On 15 January 2014 13:42, Stephan Tolksdorf <s...@quanttec.com
> <mailto:s...@quanttec.com>
Reply all
Reply to author
Forward
0 new messages