I am new to C programming, but I have programmed in some higher-level languages for a bit so I can find my way around most documentation. I'm learning about exception handling in C , specifically with this example:
vector<int> myNums;
try
{
myNums.resize(myNums.max_size() 1);
}
catch (const bad_alloc& err)
{
cout << err.what() << endl;
}
Now, this code doesn't catch the exception because the exception thrown by the .resize() method isn't bad_alloc
, it's a length_error
. My question is, from looking at this documentation, how do you get to that? Maybe I missed something obvious.
https://cplusplus.com/reference/vector/vector/resize/
The only specific exception mentioned in there is bad_alloc
. Can someone walk me through how you'd get to knowing length_error
is the correct exception starting from that page?
CodePudding user response:
This is not uncommon. The complexity of the language has increased so much over the years, accumulating multiple revisions to the C standard, that even the C standard itself can be at odds with itself, sometimes.
Let's just see what the C standard itself says about two versions of the overloaded resize()
vector method. I happen to have a copy of N4860 handy which is, basically, the C 20 version, and while looking up what the C standard itself says about resize()
's exceptions, I found that the two resize()
overloads define their exception behavior as follows:
constexpr void resize(size_type sz);
// ...
Remarks: If an exception is thrown other than by the move constructor of a non-Cpp17CopyInsertable T there are no effects.
// ...
constexpr void resize(size_type sz, const T& c);
// ...
Remarks: If an exception is thrown there are no effects.
That's the only mention of exceptions in resize()
. I found nothing on a more general in vector
itself, nor in "Container Requirements", there was some discussion of exception guarantees but none pertaining to the specific details of vector's resize()
or reserve()
.
This is an obvious oversight. It's fairly obvious that when it comes to exceptions that might be generated as a result of reallocations, both overloads should have the same exception behavior. The first overload's description is lifted straight from reserve()
that just precedes it. It goes without saying that resize()
uses reserve()
to grow the vector's capacity, when needed, and inherits its exception guarantees/behavior.
But the same thing must be true with the 2nd resize()
overload. The only difference between them is that one default-constructs new values when the vector grows and the other one copy-constructs. But in terms of exception behavior during reallocation they must be identical. The total overall difference, related to exceptions, between the two overloads is due to any exception differences between the value's default constructor and/or its copy/move constructors.
My question is, from looking at this documentation, how do you get to that? > Maybe I missed something obvious.
No, you did not miss anything. The C standard itself has some gaps; not to mention 2nd-hand sources of documentation like the one you're looking at.
You get where you want to go by studying everything about the class, template, or algorithm in question, understanding how it must work -- i.e. the resize()
s inheriting certain parts of their behavior from reserve()
-- and then drawing the inescapable inferences.
TLDR: it is what it is.
CodePudding user response:
Starting with https://cplusplus.com/reference/vector/vector/resize/, if you've got a case like yours pushing max_size()
, you might pay special attention to the case listed on this doc page which states:
If n is also greater than the current container capacity, an automatic reallocation of the allocated storage space takes place.
Since your case is absolutely going to be greater than the current container capacity, this might be worth looking into. Linked in this chunk of text is the doc page for capacity: https://cplusplus.com/reference/vector/vector/capacity/. From the capacity page, you would read that vector::reserve
is used for explicitly increasing the capacity of the vector. Since your case with max_size() 1
is certainly going to involve increasing vector capacity, you might suspect this function is involved. So you might go to the doc page: https://en.cppreference.com/w/cpp/container/vector/reserve
Here you would read that vector::reserve
takes a parameter new_cap
which determines the new capacity of a vector. It throws length_error
when new_cap > max_size()
.
I give this series of steps not because I think anyone would/should be expected to dig this much through docs every time they write code. Only because you were curious what steps might have led you to the exception that was thrown.
I agree it would be much better if the documentation for resize
just covered all it's bases with regards to which exceptions get thrown in all cases. Unfortunately, this is all too common with regards to documentation.
CodePudding user response:
You need to dig a bit to track it down.
I'm going to use documentation at cppreference, which does a reasonably decent job of tracking what happens in the standards. (The standards are the authoritative source, but the standards have evolved over time).
According to https://en.cppreference.com/w/cpp/container/vector, the second template argument when instantiating a vector is an allocator type, which defaults to std::allocator<T>
(where T
is the vector's element type).
Because the allocator is defaulted, it is not often referred to explicitly in user code (most developers do not need to use a non-default allocator).
But
std::vector<int> myNums;
is actually equivalent to
std::vector<int, std::allocator<int> > myNums;
The specification of a vector
s resize()
member function specify what happens when it throws, but not the circumstances in which it will throw - or what it may throw.
Memory allocation for std::vector
is actually handled by its reserve()
member function. Documentation for that function at https://en.cppreference.com/w/cpp/container/vector/reserve states it throws std::length_error
if new_cap > max_size()
, or any exception thrown by Allocator::allocate()
(typically std::bad_alloc
). Allocator
is the name of the second template parameter mentioned above.
That is a hint, but we can get even more specific by digging into documentation for the default allocator at https://en.cppreference.com/w/cpp/memory/allocator and for its allocate()
member function at https://en.cppreference.com/w/cpp/memory/allocator/allocate which reveals that function will throw std::bad_alloc
if allocation fails.