In item 17 of exceptional c++, I find this:
First, for all containers, multi-element inserts ("iterator range"
inserts) are never strongly exception-safe.
but in item 1 of effective STL, I find this:
If you need transactional semantics for multiple-element insertions
(e.g., the range form — see Item 5), you'll want to choose list,
because list is the only standard container that offers transactional
semantics for multiple-element insertions.
and in page 249 of the c++ standard library 2th, I find this:
For lists, even multiple-element insert operations are transaction safe.
So my question is which one is right? Is strongly exceptional-safe means the same with transaction safe?
which one is right?
For all the overloads of std::list::insert, strongly exception-safety is guaranteed.
Exceptions
If an exception is thrown, there are no effects (strong exception guarantee).
and from the standard, $23.3.5.4/2 list modifiers [list.modifiers]:
If an exception is thrown there are no effects.
then
is strongly exceptional-safe means the same with transaction safe?
Yes. Here's an explanation from Herb Sutter:
Strong Guarantee: If an exception is thrown, program state remains unchanged. This level always implies global commit-or-rollback semantics, including that no references or iterators into a container be invalidated if an operation fails.
It is already answered that std::list provides this guarantees as per standard. I'd like to mention why it is possible to do this in the list.
You can provide this guarantee because list has a constant complexity merge operation, which is a non-throwing operation. All you need to do is to first create a temporary list, fill the temporary list with values and than merge temporary list into original list.
If exception happens while populating temporary list, nothing is merged, and temporary list is simply disposed when the insert exits.
Since no other container provides constant complexity no-throwing merge, it is not possible with any other container.
Related
Firstly, assume A is a type with:
A potentially throwing copy constructor/assignment operator.
No move constructor/assignment.
This is a common example of a C++03 RAII type. Now let me cite the C++14 standard (snipped irrelevant parts):
§23.2.1 General container requirements
11 Unless otherwise specified (see ... and 23.3.6.5) all container types defined in this
Clause meet the following additional requirements:
if an exception is thrown by an insert() or emplace() function while inserting a single element, that function has no effects.
§23.3.6.5 vector modifiers
iterator insert(const_iterator position, const T& x);
...
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 while inserting a single element at the end and T is CopyInsertable or is_nothrow_move_constructible<T>::value is true, there are no effects. Otherwise, if an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.
2 Complexity: The complexity is linear in the number of elements inserted plus the distance to the end
of the vector.
Now consider this:
std::vector<A> v(5);
v.reserve(10);
v.insert(begin() + 2, A());
Clearly we're inserting a single element, so §23.2.1 - 11 applies and either the operation succeeds or v is unchanged. §23.3.6.5 doesn't change anything about this. The exception is thrown by the copy constructor. We are not inserting at the end. The move constructor is not used.
But now consider this possible scenario during the implementation of insert assuming no reallocation happens:
01234_____ initial state
0123_4____ making space by copying
012_34____ continued
012?34____ continued, but copy operation threw
At this point all future copy operations could throw, making it impossible to restore the state as required. Oops.
I can't see any implementation without reallocation that enables strong exception safety. This means that any implementation must always reallocate when inserting a type without a move constructor and a throwing copy constructor in the middle. However:
insert(pos, value) becomes unbearably slow due to constant reallocations.
The complexity requirement isn't met (reallocation always requires n operations).
It could be argued that "Causes reallocation if the new size is greater than the old capacity." implies that no reallocation is allowed if the new size is not greater than the old capacity.
To support this, consider that if an implementation may reallocate anytime, the user has no way of knowing. This makes the guarantee about preserving iterators ("If no reallocation happens, all the iterators and references before the insertion point remain valid.") useless information, and makes you wonder why both sentences were inserted into the standard in the first place.
1 & 2 are pretty damning observations, but if 3 is true then it's (as far as I can see) plain impossible to be compliant with the standard.
So, is there any way of implementing the insert method for a standards-compliant vector? Or is this a standard defect?
A demonstration of this issue can be seen here: http://coliru.stacked-crooked.com/a/afd2e838c34c8fcc
As far as my interpretation of the standard goes, here "Unless otherwise specified" means that once anything regarding exceptions is specified for insert in a corresponding clause for a particular container, the bullet point of the list in §23.2.1 is not applying anymore.
If an exception is thrown other than by the copy constructor [..]
of T [..] there are no effects.
The opposite is indicated: When an exception is thrown by the copy constructor of T there is no guarantee that the call won't have any effects. The requirement that
if an exception is thrown by an insert() or emplace() function while inserting a single element, that function has no effects
is not applicable: §23.3.6.5 specifies "otherwise".
What section of the C++11 standard (here's a copy of a draft standard) requires associative containers like std::set, std::map, std::unordered_set, and std::unordered_map to immediately call destructors of objects that are erased from them?
To put it another way - are standard-compliant associative containers allowed to delay (not elide!) their calls to the key and/or value destructors of the keys and values they store?
If not, what section in the standard forbids it?
I ask because I am interested in lazy deletions (sometimes called weak deletions) in associative containers. This is a method of "erasing" a key (or key/value pair) from a structure in which the actual data remains in place, but the node containing it is marked as dead. These are sometimes called tombstones. They are used in many theory papers about data structures, and sometimes used in practice.
A very simple example is deletion in open-addressed hash tables, which is sometimes implemented with tombstones. When the hash table is eventually rebuilt, all the destructors are called and the tombstoned key/value pairs can be actually and finally deleted and deallocated.
There are general requirements in the table for associative containers which describe the requirements for erase calls.
E.g. a.erase(q) | erases the element pointed to by q.
The element type for map is a pair of a key and a value. There is no sensible interpretation of "erases" that doesn't involve the proper destruction of the element (key and value). I doubt there is anything more explicitly worded for this situation in the standard.
I'm currently writing exception-safe code and my design requires no throw guarantee for
set::find method.
I assume the comparator object always succeeds.
Does it imply set::find method will always succeed?
I thought about such a possibility after I have seen that according to http://en.cppreference.com/w/cpp/container/set/erase, set::erase method, with the same assumption, should always succeed and maybe there's a find in it (then it would be definetely worth a comment in documentation!)
The direct source for the problem is that I need to check whether an element is in a set and remove it from the set -- all that having no throw guarantee (it's in a catch block).
std::set::find:
Return value
Iterator to an element with key key. If no such element is found, past-the-end (see end()) iterator is returned.
Neither the documentation nor the C++ Standard explicitly list any exception safety guarantees. However, the same rules spelled out for std::set::erase apply here (§23.2.4.1 Exception safety guarantees [associative.reqmts.except]):
erase(k) does not throw an exception unless that exception is thrown by the container’s Compare object (if any).
In essence, unless the Compare object throws an exception, std::set::find does not throw. Bjarne Stroustrup has the following to say in The C++ Programming Language, Special Edition - Appendix E:
Fortunately, predicates rarely do anything that might throw an exception. However, user-defined <, ==, and != predicates must be taken into account when considering exception safety.
If you aren't providing any user-defined predicates you can assume std::set::find does not throw an exception. If you are, you should mark them as noexcept to work safely in your scenario.
C++11 §23.2.4.1 Exception safety guarantees [associative.reqmts.except] lists all the exception safety requirements for associative containers (including set) and there is no mention made of find. So no, the standard does not guarantee that find never throws.
In the absence of undefined behavior and assuming a non-throwing comparator, I find it extremely unlikely that an implementation of the C++ standard library exists which will throw an exception from set::find. I would personally be comfortable with (a) wrapping set::find in a noexcept forwarding function so that the program will crash if such an "impossible" thing ever occurs, or (b) wrapping a particular set::find call in
auto it = foo.end();
try {
it = foo.find(bar);
} catch(...) {}
and simply treating an exception as "not found."
Note that the ordering relation of associative containers is an easily-overlooked source of undefined behavior: § 23.2.4/2 requires the ordering relation to induce a strict weak ordering (as described in §25.4) on the key elements. An associative container instantiated with an ordering relation that is not a strict weak ordering does not have defined behavior, one possible outcome of which is throwing an exception from find.
Clause 23.2.1.10 of C++11 standard says that
"no copy ctor of a returned iterator throws an exception"
Does this basically state that is it possible for a copy ctor of an iterator not to throw even a bad_alloc presumably (leaving the case where iterator could be just a pointer and here no issues) because it will use the information already constructed in the "returned iterator"? because it is passed by value will the stack be allocated in the called function hence can guarantee no memory issues ?
That paragraph talks about the iterators used by the containers in the standard library. These iterators are known to be implementable in ways so that they don't throw exception while being copied. For example, none of them have to use any dynamically allocated memory.
The guarantee is just for these iterators, not for iterators in general (even though it is a good idea to follow the example).
Legal answer: no. Thtat's just your interpretation. It is technically correct, but it may be not the one and only technically correct interpretation.
Technical answer: The point, here, is avoid that an exception thrown by a mutating iterator (think to an inserter or to an output iterator) causes an algorithm to be abandoned while letting a container in an undefined and inconsistent state (think, for example, to a linked list with the links not yet completely re-linked)
It's not just a matter of bad_alloc for iterators that have a dynamically allocated state, but also of an iterator that -during it's own copy- tries to modify a referred item failing in that (for example, because the item assignment throws).
When such a case happens, the iterator is not required to "complete the algorithm" (that would be impossible) but to left the container in a consistent and still manageable state.
I think there is a misinterpretation about the scope of what copy constructor means.
A copy constructor is not responsible for allocating the memory where the object itself will be built, this is provided externally, by the caller.
The requirement, therefore, is that the body of the copy constructor (be it written or generated) does not throw. It is known in C++ that built-in types (int, T*, ...) can be copied without throwing, and from there one can built types that are thus copyable without throwing exceptions (as long as one avoids dynamic resource allocation and/or IO it's automatic).
Yes, I've looked at the C++ standards that I could find (or the drafts), but I'm not finding any comprehensive of the exception guarantees given by STL containers. All I can find are occasional sections with incomplete descriptions on some of the functions for some of the types. Or perhaps it's there but I'm just not finding it, I don't know.
Note: I'm not asking for a list of all the guarantees people can think of, which is basically in this question.
I'm looking for the authoritative source of this information itself -- or preferably, a free version of the source (e.g. a draft of the standard) where I can more or less treat as official.
Reading the standard can be scary (let's come back to the standard), but Bjarne Stroustrup has written a really nice appendix on this subject in his book 'The C++ Programming Language'. He posted this appendix at
http://www.stroustrup.com/3rd_safe0.html , at
http://www.stroustrup.com/3rd_safe.pdf
It's pretty long and detailed (and well written). You may for example find section E.4 interesting, quote:
E.4 Standard Container Guarantees
If a library operation itself throws an exception, it can – and does –
make sure that the objects on which it operates are left in a
well-defined state. For example, at() throwing out_of_range for a
vector (§16.3.3) is not a problem with exception safety for the vector
. The writer of at() has no problem making sure that a vector is in a
well-defined state before throwing.
In addition, section E.4.1 states
In addition to the basic guarantee, the standard library offers the
strong guarantee for a few operations that insert or remove elements.
have a look at page 956. It contains a table of guarantees for various operations for vector, deque, list and map.
In summary, all operations on those containers are either nothrow or strong, except for N - element insert into map which offers the basic guarantees.
Note: the above text is old and does not address C++11, but should still be correct enough for most aims and purposes.
When it comes to C++11...
the standard first states, about the containers
array, deque, forward_list, list, vector, map, set, unordered_map, unordered_set, queue,stack:
at
23.2.1/10:
Unless otherwise specified (see 23.2.4.1, 23.2.5.1, 23.3.3.4, and
23.3.6.5) all container types defined in this Clause meet the following additional requirements:
— if an exception is thrown by an insert() or emplace() function while
inserting a single element, that function has no effects.
— if an exception is thrown by a push_back() or push_front() function,
that function has no effects.
— no erase(), clear(), pop_back() or pop_front() function throws an
exception.
— no copy constructor or assignment operator of a returned iterator
throws an exception.
— no swap() function throws an exception.
— no swap() function invalidates any references, pointers, or
iterators referring to the elements of the containers being swapped.
The quirks pointed out in the respective sections referred to above (each called Exception safety guarantees) are mostly about special against-the-wall cases like when dealing with exceptions from the contained types' hashing, comparison operations as well as throwing swap and throwing move operations.
n3376
23.2.1 General container requirements [container.requirements.general]
Paragraph 10
Unless otherwise specified (see 23.2.4.1, 23.2.5.1, 23.3.3.4, and 23.3.6.5) all container types defined in this Clause meet the following additional requirements:
— if an exception is thrown by an insert() or emplace() function while inserting a single element, that function has no effects.
— if an exception is thrown by a push_back() or push_front() function, that function has no effects.
— no erase(), clear(), pop_back() or pop_front() function throws an exception.
— no copy constructor or assignment operator of a returned iterator throws an exception.
— no swap() function throws an exception.
— no swap() function invalidates any references, pointers, or iterators referring to the elements of the containers being swapped.
[Note: The end() iterator does not refer to any element, so it may be invalidated. —endnote]
23.2.4 Associative containers [associative.reqmts]
23.2.4.1 Exception safety guarantees [associative.reqmts.except]
1 For associative containers, no clear() function throws an exception. erase(k) does not throw an exception unless that exception is thrown by the container’s Compare object (if any).
2 For associative containers, if an exception is thrown by any operation from within an insert or emplace function inserting a single element, the insertion has no effect.
3 For associative containers, no swap function throws an exception unless that exception is thrown by the swap of the container’s Compare object (if any).
23.2.5 Unordered associative containers [unord.req]
23.2.5.1 Exception safety guarantees [unord.req.except]
1 For unordered associative containers, no clear() function throws an exception. erase(k) does not throw an exception unless that exception is thrown by the container’s Hash or Pred object (if any).
2 For unordered associative containers, if an exception is thrown by any operation other than the container’s hash function from within an insert or emplace function inserting a single element, the insertion has no effect.
3 For unordered associative containers, no swap function throws an exception unless that exception is thrown by the swap of the container’s Hash or Pred object (if any).
4 For unordered associative containers, if an exception is thrown from within a rehash() function other than by the container’s hash function or comparison function, the rehash() function has no effect.
23.3.3.4 deque modifiers [deque.modifiers]
void push_back(T&& x); Paragraph 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-CopyInsertable T, the effects are unspecified.
iterator erase(const_iterator first, const_iterator last); Paragraph 6
Throws: Nothing unless an exception is thrown by the copy constructor, move constructor, assignment operator, or move assignment operator of T.
23.3.6.5 vector modifiers [vector.modifiers]
void push_back(T&& x); Paragraph 2
If an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.
iterator erase(const_iterator first, const_iterator last); Paragraph 5
Throws: Nothing unless an exception is thrown by the copy constructor, move constructor, assignment operator, or move assignment operator of T.
The document you've linked to, the n3337 draft standard, can be treated as official. It's the C++11 standard plus minor editorial changes.
You just need to learn to read the standard, which is understandable because it's not intended to be easy reading.
To find the exception guarantees for any particular library operation, check that operation's specification for remarks and comments on exceptions. If the function is a member function then check the specification of the type for comments on exception safety and what requirements it fulfills. Then check the fulfilled requirements for exception guarantees that must be made by objects to fulfill those requirements.
For generic types and algorithms also check the requirements placed on the template parameters in order to see what requirements those types have to meet in order for all the exception guarantees made by the type or algorithm or member function to hold (if the template parameters don't meet the specified requirements then using the template with those parameters has undefined behavior and none of the template's specifications apply).