8

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?

Community
  • 1
  • 1
NOhs
  • 2,780
  • 3
  • 25
  • 59
  • 3
    That looks like a bad example. For one thing the exception should be captured by const reference like you do but they do not. – NathanOliver Oct 25 '16 at 17:52
  • With g++ 6.2.0 on (a different distribution of) Linux, this program prints "Bad luck!". I also know that this program _should_ catch the `failure` exception and print "Bad luck!" (assuming `IDoNotExist.txt` genuinely does not exist). Therefore, your C++ compiler and/or runtime are malfunctioning. The most probable reason for this is that they are mis-installed. Try uninstalling and reinstalling every package with `g++` or `c++` in its name. – zwol Oct 25 '16 at 17:52
  • While cplusplus.com have its positive sides, it also have its negative sides (there are divided oppinions about the site, search for it if you want to know more). I tend to prefer [this reference site](http://en.cppreference.com/w/cpp) instead, and it seems to be more up to date and accurate. For example it's example on [I/O stream exceptions](http://en.cppreference.com/w/cpp/io/basic_ios/exceptions) uses the correct class to catch. – Some programmer dude Oct 25 '16 at 17:53
  • @Someprogrammerdude That example code also fails. It also does not capture by const reference. – NathanOliver Oct 25 '16 at 17:54
  • @NathanOliver That's true, but at least it catches by reference. – Some programmer dude Oct 25 '16 at 17:55
  • @zwol I reinstalled all base and base developer packages on Arch but the behaviour does not change. – NOhs Oct 25 '16 at 18:03
  • @MrZ It's probably g++ 6.2.0 bug or something: I'm also on Arch with gcc, and I get the exact same behavior. – Rakete1111 Oct 25 '16 at 18:17

2 Answers2

5

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).

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • 1
    This is the correct answer. To be completely accurate, the problem is not that `system_error` is C++11-only (that could be solved) but that `std::system_error` has a different size and layout to `std::exception` which means that `std::ios::failure` changes size depending whether it derives from `system_error` or not. That's an ABI break, so we have two different `ios::failure` types defined, and which one is visible depends on the `_GLIBCXX_USE_CXX11_ABI` macro. If libstdc++.so throws the old type and you try to catch the new type (or vice versa) it fails. But I used magic in GCC 8 so it works – Jonathan Wakely Apr 10 '18 at 15:12
  • 1
    Should catch const exception: `catch (const std::exception &e)` – BЈовић Apr 10 '18 at 15:40
3

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™.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521