I'm wondering, exactly what is the exception safety guarantee for std::vector::insert
? I am interested in both the single-argument and the range overloads of this function.
vector insert() function in C++ STL std::vector::insert() is a built-in function in C++ STL that inserts new elements before the element at the specified position, effectively increasing the container size by the number of elements inserted.
Exception safe programming is programming so that if any piece of code that might throw an exception does throw an exception, then the state of the program is not corrupted and resources are not leaked. Getting this right using traditional methods often results in complex, unappealing and brittle code.
Strong exception guarantee -- If the function throws an exception, the state of the program is rolled back to the state just before the function call. (for example, std::vector::push_back) Basic exception guarantee -- If the function throws an exception, the program is in a valid state.
insert(): Inserts new elements into the vector at a particular position. ts time complexity is O(N + M) where N is the number of elements inserted and M is the number of the elements moved . pop_back(): Removes the last element from the vector. Its time complexity is O(1).
Generally, the single-element form of insert
has a strong exception guarantee for any container as per [container.requirements.general]/10, but vector::insert
is an exception to this rule:
[vector.modifiers]/1 applies to vector::insert
; InputIterator
here refers to the overload of insert
that inserts a range.
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.
vector
is an allocator-aware container, which means that it uses an allocator for memory allocation and construction of its elements [container.requirements.general]/3
For the components affected by this subclause that declare an
allocator_type
, objects stored in these components shall be constructed using theallocator_traits<allocator_type>::construct
function and destroyed using theallocator_traits<allocator_type>::destroy
function. These functions are called only for the container's element type, not for internal types used by the container.
I think this implies that local objects, which are not elements of the container, can be created without using the allocator (e.g. for copy-and-swap). Otherwise, the requirements on the ctors of the value type would be pointless; the allocator's construct
function might have different exception guarantees than the value type's ctor.
CopyInsertable
is specified in [container.requirements.general]/13 by requiring that
allocator_traits<A>::construct(m, p, v);
is well-formed; where A
is the allocator type, m
is of type A
, p
is a pointer to T
, v
is an expression of type (const
) T
, and T
is the value type of the container. This is an inplace construction from one argument (copy- or move-construction).
Similarly, MoveConstructible
is specified, but v
is (always) an rvalue of type T
. EmplaceConstructible
follows the same form for zero or more arguments instead of v
.
The insert
function for sequence containers imposes different requirements on the value type for its various forms [sequence.reqmts]; here including additional requirements for vector
:
const
) T
and for the form insert N copies of, T
shall be CopyInsertable
and CopyAssignable
T
, T
shall be MoveInsertable
and MoveAssignable
T
shall be EmplaceConstructible
from the dereferenced iterators (*); additionally, MoveInsertable
and MoveAssignable
if the iterators of the range are no forward iterators(*) Note: EmplaceConstructible
only from the dereferenced iterators is not enough if the container has to be resized for the insertion (and, e.g. the *i
for such an iterator is not of the value type). It is possible that the specification requires the range form to inherit the requirements of the single-element form, i.e. either MoveAssignable
or CopyAssignable
.
Side remark: The range form of insert
requires that the two iterators do not point in the container in which they are to be inserted.
I interpret the exception specifications as follows:
The additional statement about CopyInsertable
in the exception specification of vector::insert
probably distinguishes between the basic guarantee and no guarantee: The dtor of a container is generally required to call the dtor of all elements and deallocate all memory (in the general container requirements). That is, unless the behaviour is unspecified/undefined, the basic guarantee holds.
Why there is a requirement that combines CopyInsertable
and the move-ctor (instead of allocator::construct
with an rvalue), I don't know. The move-ctor is only used directly for objects that are not elements of the container (indirectly possibly via allocator::construct
).
The other remarks about "no effects" (-> strong guarantee) are not applied to the allocator operations (construct
). The allocator operations obviously do not have to be noexcept. As you can provide a non-default allocator that doesn't use the value type's copy- and move-ctor for construct
, insert
has to provide the strong exception guarantees even for the construct
allocator operations. For example, if the allocator's construct
is not noexcept, during a resize, the elements cannot be move-constructed but have to be copied.
Move- and copy-assignment have to be noexcept for the strong guarantee as the elements might need to be shifted around for insert
; copy- and move-ctor might need to be noexcept for the strong guarantee because of local objects created in the algorithm.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With