I was running a MWE from here: http://www.cplusplus.com/reference/ios/ios/exceptions/ On my machine it does not catch the exception. Here is my code
#include <iostream>
#include <fstream>
int main()
{
std::ifstream file;
file.exceptions( std::ifstream::failbit | std::ifstream::badbit );
try
{
file.open("IDoNotExist.txt");
}
catch(const std::ifstream::failure& e)
{
std::cout << "Bad luck!" << std::endl;
}
}
Using gcc 6.2.1
on Arch-Linux I get:
terminate called after throwing an instance of 'std::ios_base::failure'
what(): basic_ios::clear
However, on the link posted above it is mentioned that the code should also catch the exception related to opening the file. What went wrong?
It looks like a known bug in libstdc++.
The problem is that with the change to the C++11 ABI, many classes were duplicated in libstdc++6.so
, one version with the old ABI, other with the new one.
Exception classes were not duplicated so this problem didn't exist at the time. But then, in some newer revision of the language, it was decided that std::ios_base::failure
should derive from std::system_error
instead of std::exception
... but system_error
is a C++11 only class so it must use the new ABI flag or it will complain. Now you have two different std::ios_base::failure
classes and a mess in your hands!
The easy solution is to compile your program with -D_GLIBCXX_USE_CXX11_ABI=0
and resign to the old ABI until the bug is solved. Or alternatively, write catch (std::exception &e)
.
N.B. std::ifstream::failure
is a type defined in the std::ios_base
base class of ifstream
, so the rest of this answer refers to it as std::ios_base::failure
or just std::ios::failure
.
The problem here is that since GCC 5 there are two different definitions of std::ios_base::failure
in libstdc++ (see the Dual ABI docs for more details). That's needed because C++11 changed the definition of ios::failure
from:
class ios_base::failure : public exception {
to:
class ios_base::failure : public system_error {
This is an ABI change, because system_error
has additional data members compared to exception
, and so it changes the size and layout of ios::failure
.
So since GCC 5.1, when your code names std::ios_base::failure
(or std::ifstream::failure
or any other name for it) which definition you get depends on the value of the _GLIBCXX_USE_CXX11_ABI
macro. And when an iostream error happens inside the libstdc++.so
library, which type gets thrown depends on the value of the macro when libstdc++.so
was built. If you try to catch one type and the library throws the other type, the catch won't work. This is what you're seeing. In your code std::ifstream::failure
names the new type, but the library is throwing the old type, which is a different class, so the catch handler isn't matched.
With GCC 5.x and 6.x the code inside libstdc++.so
throws the old type, so to catch it you need to either compile you code with -D_GLIBCXX_USE_CXX11_ABI=0
or change your handler to catch (const std::exception&)
(because both the old and new types derive from std::exception
).
With GCC 7.x the code inside the library throws the new type (changed by PR 66145), so you need to compile your code with -D_GLIBCXX_USE_CXX11_ABI=1
or catch std::exception
.
For GCC 8.x the library now throws an exception type that can be caught by a handler for the old type or the new type (changed by PR 85222), so you don't need to change your code. It will Just Work™.
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