11

Consider:

std::vector<int> v;
v.reserve(1);
v.push_back(1); // is this statement guaranteed not to throw?

I've chosen int because it has no constructors that could throw - obviously if some copy constructor of T throws, then that exception escapes vector<T>::push_back.

This question applies as much to insert as push_back, but it was inspired by Is it safe to push_back 'dynamically allocated object' to vector?, which happens to ask about push_back.

In the C++03 and C++0x standard/FCD, the descriptions of vector::insert say that if no reallocation happens, iterators/references before the insertion point remain valid. They don't say that if no reallocation happens, no exception is thrown (unless from constructors etc of T).

Is there anything elsewhere in the standard to guarantee this?

I don't expect push_back to do anything that could throw in this case. The GNU implementation doesn't. The question is whether the standard forbids it.

As a follow-up, can anyone think of a reason why any implementation would throw? The best I can think of, is that if a call to reserve ends up increasing the capacity to a value in excess of max_size(), then insert perhaps is permitted to throw length_error when the max size would be exceeded. It would be useless to increase capacity beyond max_size(), but I don't immediately see anything forbidding that, either [Edit: your allocator would probably stop you increasing capacity beyond max_size, so this suggestion might be no good.]

Community
  • 1
  • 1
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 1
    Excluding copy constructors that might throw during push_back? – Flexo Nov 15 '10 at 16:28
  • @awoodland: Steve is specifically using `int`s to take that off the table – John Dibling Nov 15 '10 at 16:31
  • @awoodland: yes, and as John says I've picked a specific example which ignores all that, for simplicity. – Steve Jessop Nov 15 '10 at 16:35
  • @Steve Jessop: RE your last paragraph, `reserve` is defined to throw `length_error` if it would exceed `max_size()` (this is independent of anything the allocator may throw). – Steve M Nov 15 '10 at 17:04
  • @Steve M: I refer to the "pedantic" tag ;-) `reserve` is defined to throw if the *argument* exceeds `max_size()`, but capacity afterwards can be greater than the argument. So this odd situation, if legal, wouldn't cause `push_back` to throw in my `int` example, or in any example where you had passed a big enough value to `reserve`, but might permit it to throw in odd situations where `capacity()` is greater than `size()`, and hence the vector is not permitted to reallocate. – Steve Jessop Nov 15 '10 at 17:08
  • @Steve Jessop: Haha, well, within the context of your proposed solution to that other question, I think that situation won't have an impact. If I say `v.reserve(v.size()+1)` without it throwing, `v.capacity()` must be at least 1 bigger than the previous size, which means that I can add 1 more element without reallocating. – Steve M Nov 15 '10 at 17:25
  • @Steve M: agreed, my suggestion for the "follow-up" wouldn't throw in the situation in that other question. I've not really come up with anything that throws at all, let alone in that particular case. – Steve Jessop Nov 15 '10 at 17:28

1 Answers1

1

Well, it kind of depends on the allocator you are using.

Apart from the allocator, the only thing you can rely on is that push_back() and push_front() are guaranteed to be noop if an exception is thrown (23.1-10). The standard definitely doesn't forbid the push_back() from throwing exceptions.

Šimon Tóth
  • 35,456
  • 20
  • 106
  • 151
  • @Let_Me_Be: depends on the allocator in what way? Are you in effect saying that `push_back` (and presumably any other function of any container) can make arbitrary calls to the allocator that might throw, even when not reallocating? I suppose possibly I should be talking about `Alloc::construct` rather than constructors of T, but I think an allocator which throws in `construct` when the relevant constructor of T would not, is violating the standard anyway. I don't care what happens in non-conforming programs :-) – Steve Jessop Nov 15 '10 at 16:34
  • @Steve Well, the standard explicitly talks about the fact that insert/push_back are noops when an exception is thrown (other than in constructor or assignment of T). On the other hand, the reallocation should only happen when the new size is greater than old capacity. It doesn't make sense to throw anything, but it is obviously allowed. – Šimon Tóth Nov 15 '10 at 16:41
  • That's my thought too, it's allowed by those clauses about insert. So the only thing that could possibly forbid it, is the other 781 pages of the standard ;-) For instance, if there was some general statement about when vectors can throw. I'm certainly not expecting any general statement about containers or sequences, since `deque` and `list` have no capacity, so for them, `push_back` must always be allowed to allocate memory. – Steve Jessop Nov 15 '10 at 16:45
  • @Steve I'm not sure about this, but I think system/CPU exceptions can be mapped into C++ runtime, so if they are, this is one case when push_back can throw. – Šimon Tóth Nov 15 '10 at 17:00
  • @Let_Me_Be: System/CPU exceptions being mapped to C++ exceptions are a compiler specific extension to the language (so not technically part of C++). SO unless the allocator throws because it ran out of room or T throws during construction no other exceptions should be coming your way. – Martin York Nov 15 '10 at 17:12
  • @Martin Well, everything is in a sense a compiler specific extension :-D The question is if it does conform to the standard or not. – Šimon Tóth Nov 15 '10 at 17:16
  • 1
    As mitigation, though, it isn't possible to write code that's exception-safe under the assumption that an exception could occur literally anywhere: `std::swap (int &lhs, int &rhs) { int tmp = rhs; rhs = lhs; /* what if an exception occurs here */ lhs = tmp; }`. So I'd be very cross with the compiler vendor, and I would certainly claim that my program was conforming and has the right to expect that `swap` doesn't set one int equal to the other and then throw. It's another thing entirely to throw an exception where behavior is undefined (e.g. division by 0). – Steve Jessop Nov 15 '10 at 17:31
  • (note: don't tell me I'm not allowed to specialize `std::swap` ;-). I just mean to illustrate that's what `std::swap` actually does, and that if I wrote that code under another name I could be rightly upset when it failed) – Steve Jessop Nov 15 '10 at 17:36
  • -1 The allocator is called during the `reserve(1)`, and is not called during the `push_back()` (as a result of calling reserve()) so a throwing allocator doesn't matter in this case. – Sjoerd Nov 17 '10 at 08:42
  • @Sjoerd: The allocator's `allocate` function is called during `reserve`. But its `construct` function is called during `push_back`. – Ben Voigt Sep 13 '13 at 19:48