关于ifstream:使用C ++打开不存在的文件时未捕获到异常

Exception not caught opening a non-existing file using C++

我从这里开始运行MWE:
http://www.cplusplus.com/reference/ios/ios/exceptions/
在我的机器上,它没有捕获异常。 这是我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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;
    }
}

在Arch-Linux上使用gcc 6.2.1,我得到:

terminate called after throwing an instance of 'std::ios_base::failure'

what(): basic_ios::clear

但是,在上面发布的链接上,提到该代码还应该捕获与打开文件有关的异常。 什么地方出了错?


它看起来像libstdc ++中的一个已知错误。

问题在于,随着对C ++ 11 ABI的更改,许多类在libstdc++6.so中被复制,一个版本与旧的ABI,另一版本与新的ABI。

异常类没有重复,因此该问题当时不存在。但是,随后,在该语言的一些较新版本中,决定std::ios_base::failure应该从std::system_error而不是std::exception派生...但是system_error是仅C ++ 11的类,因此它必须使用新的ABI标志,否则会抱怨。现在您有两个不同的std::ios_base::failure类,并且手头一团糟!

一种简单的解决方案是使用-D_GLIBCXX_USE_CXX11_ABI=0编译您的程序,然后返回旧的ABI,直到解决了该错误。或者,编写catch (std::exception &e)


N.B. std::ifstream::failure是在ifstreamstd::ios_base基类中定义的类型,因此此答案的其余部分将其称为std::ios_base::failure或仅仅是std::ios::failure

这里的问题是,由于GCC 5在libstdc ++中有std::ios_base::failure的两个不同定义(有关更多详细信息,请参见Dual ABI文档)。这是必需的,因为C ++ 11将ios::failure的定义从:

1
class ios_base::failure : public exception {

至:

1
class ios_base::failure : public system_error {

这是ABI的更改,因为与exception相比system_error具有更多的数据成员,因此它更改了ios::failure的大小和布局。

因此,从GCC 5.1开始,当您的代码名称std::ios_base::failure(或std::ifstream::failure或它的任何其他名称)时,您得到的定义取决于_GLIBCXX_USE_CXX11_ABI宏的值。而且,当libstdc++.so库内部发生iostream错误时,抛出哪种类型取决于生成libstdc++.so时宏的值。如果尝试捕获一种类型,而库抛出另一种类型,则捕获将不起作用。这就是您所看到的。在您的代码中,std::ifstream::failure命名了新类型,但是库抛出了旧类型,这是一个不同的类,因此catch处理程序不匹配。

使用GCC 5.x和6.x,libstdc++.so中的代码会抛出旧类型,因此要捕获它,您需要使用-D_GLIBCXX_USE_CXX11_ABI=0编译代码或将处理程序更改为catch (const std::exception&)(因为新旧版本均如此)类型源自std::exception)。

使用GCC 7.x,库中的代码将引发新类型(由PR 66145更改),因此您需要使用-D_GLIBCXX_USE_CXX11_ABI=1编译代码或捕获std::exception

对于GCC 8.x,库现在引发了一个异常类型,该异常类型可以由处理程序捕获为旧类型或新类型(由PR 85222更改),因此您无需更改代码。它会工作吗?