The pop() method of std::priority_queue is not declared noexcept, so in theory could throw an exception. But when might it throw an exception, and what might those exceptions be?
It could be marked nothrow, but isn't.
Why std::priority_queue::pop could* not throw
void pop();
Removes the top element from the priority queue. Effectively calls
std::pop_heap(c.begin(), c.end(), comp); c.pop_back();
c is by default an std::vector.
[vector.modifiers]/4&5
void pop_back();
4/ Complexity: The destructor of T is called the number of times equal to the number of the elements erased, but the assignment operator of T is called the number of times equal to the number of elements in the vector after the erased elements.
5/ Throws: Nothing unless an exception is thrown by the assignment operator or move assignment operator of T.
*So only the destructor of T is called and that one cannot throw because of
[requirements.on.functions]/2.4
2/ In particular, the effects are undefined in the following cases:
[...]
2.4/ if any replacement function or handler function or destructor operation exits via an exception, unless specifically allowed in the applicable Required behavior: paragraph.
Why is std::priority_queue::pop not nothrow?
Since an exception thrown from T::~T would lead to UB, the implementation can assume it cannot happen and still conform to the Standard. Another way to deal with it is to let such library functions nothrow(false) and not dealing with it.
Related
The c++ reference for std::accumulate does not mention any exception to be possibly thrown by std::accumulate, still its definition does not contain noexcept. Assuming one uses types and operations which do not throw, is safe to employ std::accumulate in a member function declared as noexcept, or does one incur in UB?
For example:
#include <iostream>
#include <numeric>
#include <vector>
class A {
std::vector<int> m_v;
public:
A(std::size_t N) : m_v(N, 1) { }
int sum() const noexcept { return std::accumulate(m_v.begin(), m_v.end(), 0); }
};
int main()
{
A x{3};
std::cout << x.sum() << std::endl;
return 0;
}
Is declaring A::sum() as noexcept a source of UB?
There are preconditions on std::accumulate(), e.g., that the end of the range is reachable from the begin of the range. So far the standard library hasn’t put noexcept on functions with preconditions (at least, not in general; there may be special cases) as a debugging implementation could assert that there is a problem, e.g., by throwing an exception: what happens when undefined behavior is triggered is undefined and implementations are free to define any of that if they wish to do so.
Also, any of the functions called by std::accumulate() are allowed to throw, i.e., any noexcept declaration would need to be conditional. I think it is unlikely that the algorithms will get corresponding noexcept declarations in general.
As the specification doesn’t mention any case where std::accumulate() throws when called within contract, it won’t throw if none of the called operations throws.
Yes, in general it can.
First of all the operations that are customizable through the template arguments and that std::accumulate must perform can throw.
But even aside from that the standard does allow an implementation to throw implementation-defined exceptions if a standard library function does not have a non-throwing exception specification and the description of the function doesn't say otherwise, see [res.on.exception.handling]/4.
That being said, I would be surprised if the use of std::accumulate in your example would throw. There is no dynamic allocation required, so a potential throwing of std::bad_alloc should not be necessary and that is the most likely candidate for implementation-defined exceptions. The summation, copy and/or move operations on ints are also non-throwing.
In any case adding noexcept to a function does not cause undefined behavior if an exception is thrown inside of it. Instead it is well-defined that, should an exception reach the noexcept function's outer scope, std::terminate will be called, which by default abort the program, but can be customized to some degree.
I've checked a lot of move constructor/vector/noexcept threads, but I am still unsure what actually happens when things are supposed to go wrong. I can't produce an error when I expect to, so either my little test is wrong, or my understanding of the problem is wrong.
I am using a vector of a BufferTrio object, which defines a noexcept(false) move constructor, and deletes every other constructor/assignment operator so that there's nothing to fall back to:
BufferTrio(const BufferTrio&) = delete;
BufferTrio& operator=(const BufferTrio&) = delete;
BufferTrio& operator=(BufferTrio&& other) = delete;
BufferTrio(BufferTrio&& other) noexcept(false)
: vaoID(other.vaoID)
, vboID(other.vboID)
, eboID(other.eboID)
{
other.vaoID = 0;
other.vboID = 0;
other.eboID = 0;
}
Things compile and run, but from https://xinhuang.github.io/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html:
std::vector will use move when it needs to increase(or decrease) the capacity, as long as the move operation is noexcept.
Or from Optimized C++: Proven Techniques for Heightened Performance By Kurt Guntheroth:
If the move constructor and move assignment operator are not declared noexcept, std::vector uses the less efficient copy operations instead.
Since I've deleted those, my understanding is that something should be breaking here. But things are running ok with that vector. So I also created a basic loop that push_backs half a million times into a dummy vector, and then swapped that vector with another single-element dummy vector. Like so:
vector<BufferTrio> thing;
int n = 500000;
while (n--)
{
thing.push_back(BufferTrio());
}
vector<BufferTrio> thing2;
thing2.push_back(BufferTrio());
thing.swap(thing2);
cout << "Sizes are " << thing.size() << " and " << thing2.size() << endl;
cout << "Capacities are " << thing.capacity() << " and " << thing2.capacity() << endl;
Output:
Sizes are 1 and 500000
Capacities are 1 and 699913
Still no problems, so:
Should I see something going wrong, and if so, how can I demonstrate it?
A vector reallocation attempts to offer an exception guarantee, i.e. an attempt to preserve the original state if an exception is thrown during the reallocation operation. There are three scenarios:
The element type is nothrow_move_constructible: Reallocation can move elements which won't cause an exception. This is the efficient case.
The element type is CopyInsertable: if the type fails to be nothrow_move_constructible, this is sufficient to provide the strong guarantee, though copies are made during reallocation. This was the old C++03 default behaviour and is the less efficient fall-back.
The element type is neither CopyInsertable nor nothrow_move_constructible. As long as it is still move-constructible, like in your example, vector reallocation is possible, but does not provide any exception guarantees (e.g. you might lose elements if a move construction throws).
The normative wording that says this is spread out across the various reallocating functions. For example, [vector.modifiers]/push_back says:
If an exception is thrown while
inserting a single element at the end and T is CopyInsertable or is_nothrow_move_constructible_v<T> 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.
I don't know what the authors of the posts you cite had in mind, though I can imagine that they are implicitly assuming that you want the strong exception guarantee, and so they'd like to steer you into cases (1) or (2).
There is nothing going wrong in your example. From std::vector::push_back:
If T's move constructor is not noexcept and T is not CopyInsertable into *this, vector will use the throwing move constructor. If it throws, the guarantee is waived and the effects are unspecified.
std::vector prefers non-throwing move constructors, and if none is available, will fall back on the copy constructor (throwing or not). But if that is also not available, then it has to use the throwing move constructor. Basically, the vector tries to save you from throwing constructors and leaving objects in an indeterminate state.
So in that regard, your example is correct, but if your move constructor actually threw an exception, then you'd have unspecified behavior.
TLDR So long as a type is MoveInsertable, you're okay, and here you're okay.
A type is MoveInsertable if, given the container's allocator A, an instance of the allocator assigned to variable m, a pointer to T* called p, and an r-value of type T the following expression is well-formed:
allocator_traits<A>::construct(m, p, rv);
For your std::vector<BufferTrio>, you are using the default std::allocator<BufferTrio>, so this call to construct calls
m.construct(p, std::forward<U>(rv))
Where U is a forwarding reference type (in your case it's BufferTrio&&, an rvalue reference)
So far so good,
m.construct will use placement-new to construct the member in-place([allocator.members])
::new((void *)p) U(std::forward<Args>(args)...)
At no point does this require noexcept. It's only for exception guarantee reasons.
[vector.modifiers] states that for void push_back(T&& x);
If an exception is thrown by the move
constructor of a non-CopyInsertable T, the effects are unspecified.
Finally,
Regarding your swap (Emphasis mine):
[container.requirements.general]
The expression a.swap(b), for containers a and b of a standard container type other than array, shall exchange
the values of a and b without invoking any move, copy, or swap operations on the individual container
elements.
Does the standard guarantee that string::erase and string::pop_back DO NOT reallocate memory? After erasing some elements, is it possible that the string might be shrink automatically?
I checked the standard, it says that string::erase and string::pop_back either throw std::out_of_range or throw nothing. Can I take that as a guarantee that these methods DO NOT do any reallocation? Since the reallocation might throw bad_alloc.
No, sensible implementations might not reallocate but the standard does not guarantee that these method calls do not reallocate, the standard says on the requirements:
References, pointers, and iterators referring to the elements of a
basic_string sequence may be invalidated by the following uses of
that basic_string object:
(4.1)
as an argument to any standard library function taking a reference to non-const basic_string as an argument.227
(4.2)
Calling non-const member functions, except operator[], at, data, front, back, begin, rbegin, end, and rend.
Both proposed methods fall under category 2 and thus both might change capacity() which would implicitly mean a reallocation.
pop_back is required to have the same effect as erase as erase is specified as :
Effects: Determines the effective length xlen of the string to be
removed as the smaller of n and size() - pos. 3
The function then replaces the string controlled by *this with a string of length size() - xlen whose first pos elements are a copy of
the initial elements of the original string controlled by *this, and
whose remaining elements are a copy of the elements of the original
string controlled by *this beginning at position pos + xlen.
There's no guarantee on how that copy is made, extra allocations or reallocations are thus possible.
As for
Can I take that as a guarantee that these methods DO NOT do any
reallocation? Since the reallocation might throw bad_alloc.
The standard does not seem to explicitly mention the possibility of a bad_alloc caused by any of the methods. Even with a binding reserve call there is no mention of it:
void reserve(size_type res_arg=0);
Throws: length_error if res_arg > max_size()
Thus I don't think that assumption can be made.
Yes, you may assume that the Throws: on these functions prohibits reallocation (which could throw bad_alloc).
20.5.5.12 Restrictions on exception handling [res.on.exception.handling]
Any of the functions defined in the C++ standard library can report a
failure by throwing an exception of a type described in its Throws:
paragraph, or of a type derived from a type named in the Throws:
paragraph that would be caught by an exception handler for the base
type.
...
Functions defined in the C++ standard library that do not have a
Throws: paragraph but do have a potentially- throwing exception specification may throw implementation-defined
exceptions.186 Implementations should report errors by
throwing exceptions of or derived from the standard exception classes
(21.6.3.1, 21.8, 22.2).
Note that the spec does not explicitly say that something marked with a "Throws: Nothing." can't throw anything. But at some point common sense has to take over. The standard also doesn't explicitly state that these functions can't reformat your disk. In general the standard specifies what the functions can do, and they are not allowed to do anything else.
The only way a function throws an exception not listed in the Throws: spec (or a derived type) is if the client uses it in a way that invokes undefined behavior.
I think it's guaranteed, even the standard doesn't say that explicitly. Here's a description about the effect of reallocation on shrink_to_fit:
Remarks: Reallocation invalidates all the references, pointers, and iterators referring to the elements in the sequence as well as the past-the-end iterator. If no reallocation happens, they remain valid.
If reallocation happends, all the iterators, pointers, references will be invalidated. But erase and pop_back don't mention that; that means they won't cause all of them to be invalidated, then reallocation won't happen.
Reading documentation, I think it depends on the signature of the function. In the case basic_string& erase(size_type pos = 0, size_type n = npos); it's described as a copy of the previous string. In the case iterator erase(const_iterator first, const_iterator last); the elements are removed.
Actually I'm quite surprised by the difference. I think you can't be sure that there is no allocation.
EDIT Note that in this small example, I keep the same address at each check:
#include <iostream>
int main()
{
std::string a;
for(std::size_t i = 0; i < 10000; ++i)
a += "Hello World ! My name is Bond... James Bond.";
std::cout << (long)&a[0] << std::endl;
a.erase(400000);
std::cout << (long)&(a[0]) << std::endl;
a.erase(10);
std::cout << (long)&(a[0]) << std::endl;
return 0;
}
I am trying to understand how exceptions affect an std::vector. More precisely, I want to check the size of the vector, when an out of memory exception is thrown.
I mean something like this:
std::vector<int> v;
try {
for(unsigned int i = 0; i < desiredSize; ++i)
v.push_back(i);
}
catch (const std::bad_alloc&) {
cerr << "Out of memory! v.size() = " << v.size() << endl;
exit(EXIT_FAILURE);
}
Is that a good approach or should I keep track of the vector's size with an independent variable of mine?
From the documentation for std::vector::push_back:
If an exception is thrown (which can be due to Allocator::allocate() or element copy/move constructor/assignment), this function has no effect (strong exception guarantee).
So in case of failure, the last push_back which caused the exception will be rolled back, but everything else will be fine: your vector will contain all of the previously pushed elements, and will be in a consistent state.
According to [vector.modifiers] (emphasis mine):
Remarks: Causes reallocation if the new size is greater than the old capacity.
Reallocation invalidates all the references, pointers, and iterators referring to the elements in the sequence.
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_v<T> 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.
Since your T is int (and operations on integers never throw) you can only get the out-of-memory errors from std::vector when it attempts to allocate new memory for its contents, hence this function has no effect when throwing any exception and using size() afterwards is a perfectly valid approach.
Exceptions
If an exception is thrown (which can be due to Allocator::allocate() or element copy/move constructor/assignment), this function has no effect (strong exception guarantee).
If T's move constructor is not noexcept and T is not CopyInsertable into *this, vector will use the throwing move constructor. If it throws, the guarantee is waived and the effects are unspecified.
I have read that std::vector erase method use move operations only if type is known to not emit exceptions due to strong exception safety. Other comments are that erase method guarantee basic or no throw exception safety depending on that if element constructor throws or not. I wasn't able to clarify that in my C++11 draft. I did test and it shows basic exception safety guarantee, it also used move constructor which was not marked as noexcept. Did I overlook something ? What is right ?
Table 100 -- Sequence container requirements in section 23.2.3 [sequence.reqmts] says:
a.erase(q)
Requires: For vector and deque, T shall be MoveAssignable.
This means that the implementation can call no operations on T except to destruct it or move assign it. Note that if the implementation move assigns T that doesn't guarantee that a move assignment operator will be called. For example T may not have a move assignment operator, and so in that case a copy assignment operator could be called. However the implementation is not allowed to copy assign the T, only move assign it.
*i = std::move(*j); // implementation can do this
*i = *j; // implementation can not do this
Furthermore 23.3.6.5 vector modifiers [vector.modifiers] says the following:
iterator erase(const_iterator position);
iterator erase(const_iterator first, const_iterator last);
Throws: Nothing unless an exception is thrown by the copy constructor, move constructor, assignment operator, or move assignment
operator of T.
I must admit I sighed when I read this. There is clearly a minor defect here. This operation is not allowed to form any expressions that would directly construct a T. Perhaps one is constructed as an implementation detail inside an assignment operator of T, but that is of no concern to this specification. The concern is does this expression throw or not:
*i = std::move(*j); // implementation can do this. Will it throw?
If that expression (where i and j are iterators referring to a T) does not throw, then vector::erase has the no-throw guarantee. Otherwise vector::erase has the basic exception safety guarantee.
Note that for this operation, the implementation is not allowed to fall back to copy assignment if is_nothrow_move_assignable<T>::value is false. Such logic is present in other vector operations such as push_back, but not here.
Also note the Complexity specification of this same section:
Complexity: The destructor of T is called the number of times equal to the number of the elements erased, but the move assignment
operator of T is called the number of times equal to the number of
elements in the vector after the erased elements.
Restated: If you erase a range of elements that end with the end of the vector, zero move assignments will be performed, and move assignment is the only thing that might throw. So you get back the no-throw guarantee if you are erasing at the end, even if is_nothrow_move_assignable<T>::value is false.
23.3.6.5 Throws: Nothing unless an exception is thrown by the copy constructor, move constructor, assignment operator, or move assignment operator of T.
Provided your implementation conforms to this, it may implement the erase as it sees fit. There is no implicit exception safety guarantee as far as I can tell.