user1641854 user1641854 - 3 months ago 17
C++ Question

c++ std::ios_base::failure exception

Standard(

N3337
) says (
27.5.3.1.1 Class ios_base::failure
):


The class failure defines the base class for the types of all objects
thrown as exceptions, by functions in the iostreams library, to report
errors detected during stream buffer operations.


I have a simple test program which emulates restricted resource environment while using of std::ostringstream:

#include <sys/time.h>
#include <sys/resource.h>

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include <iostream>
#include <sstream>

int main(int argc, const char* argv[])
{
rlimit limit;
limit.rlim_cur = limit.rlim_max = 268435456;

if(setrlimit(RLIMIT_AS, &limit)) {
std::cerr << "Cannot set resource limit: " << strerror(errno) << std::endl;
exit(EXIT_FAILURE);
}

std::ostringstream os;
os.exceptions(std::ostringstream::badbit);

try {
auto iterations = 1024 * 1024 * 1024;

while(iterations && --iterations) os << 'F';

} catch(const std::ios_base::failure& ex) {
std::cerr << "Caught: std::ios_base::failure" << std::endl;
} catch(const std::bad_alloc& ex) {
std::cerr << "Caught: std::bad_alloc" << std::endl;
} catch(...) {
std::cerr << "Caught: ellipsis" << std::endl;
}

return 0;
}


In my environment (Linux, gcc 5.3.0) I got
Caught: std::bad_alloc
on
stderr
. One of online compilers shows the same output.

The question is: why exception type is
std::bad_alloc
and not
std::ios_base::failure
?

Answer

os << 'F'; is operator<<(ostream&, char), which is a Formatted Output Function, and, quoting 27.7.3.6.1[ostream.formatted.reqmts],

the function endeavors to generate the requested output. If the generation fails, then the formatted output function does setstate(ios_base::failbit), which might throw an exception. If an exception is thrown during output, then ios::badbit is turned on without causing an ios::failure to be thrown. in *this’s error state. If (exceptions()&badbit) != 0 then the exception is rethrown

As part of output, this function calls stringbuf::overflow, which is specified, in 27.8.2.4[stringbuf.virtuals]p8, to perform reallocation. The difference between libstdc++ and libc++ here is the interpretation of the consequences of its allocation failure:

in libstdc++, it throws std::bad_alloc out of stringbuf::overflow, which unrolls the stack all the way to operator<< (technically, __ostream_insert), sets badbit and is rethrown, unmodified, as specified above.

In libc++, std::bad_alloc is caught inside stringbuf::overflow, and it makes overflow return traits::eof, which, in turn, makes the caller (in this case, steambuf::xsputn) return zero, which, in turn, makes the caller, __pad_and_output, wipe out the stream's rdbuf completely, which, in turn, makes its caller, __put_character_sequence, set both badbit and failbit. Setting of that badbit throws the ios::failure you caught.

Perhaps libc++ is technically correct in stringbuf::overflow: the standard says

''Returns:'' traits::eof() to indicate failure.

and it's hard to imagine a way for it to fail other than by allocation failure, but I think libstdc++'s interpretation is closer to the intent. (In libstdc++, stringbuf::overflow can still return eof, if the buffer capacity reaches string::max_size without first hitting bad_alloc)