添加ifstream exceptions后循环抛出std::ios_base::failure异常的原因
问题描述
我编写了如下C++代码,用于练习try/catch处理ifstream与ofstream的文件操作异常:
int main() { string path = "D:\\myFile.txt"; Point pointOfIllusion(1, 2, 3); Point pointOfDesilusion(3, 2, 1); ofstream fout; fout.exceptions(ofstream::badbit | ofstream::failbit); // everything is ok with this string try { cout << "Trying to read the file\n"; fout.open(path, ofstream::app); cout << "The file has been successfully created and opened\n"; } catch(const ofstream::failure& ex){ cout << ex.what() << endl; cout << ex.code() << endl; perror ("Could not open the file"); abort(); } fout.write((char*)&pointOfIllusion, sizeof(Point)); fout.write((char*)&pointOfDesilusion, sizeof(Point)); fout.close(); ifstream fin; fin.exceptions(ifstream::badbit | ifstream::failbit); // If I put this, it causes an error, described below, but without this string the following try/catch doesn't work try { cout << "Trying to read the file\n"; fin.open(path); cout << "The file has been successfully opened\n"; } catch (const ifstream::failure& ex) { cout << ex.what() << endl; cout << ex.code() << endl; perror ("Could not open the file"); abort(); } Point tempPoint; size_t length = 0; while (fin.read((char*)&tempPoint, sizeof(Point))) { // The debugger throws the exception when I add the string above in this place, however I do not modify it at all length++; } fin.clear(); fin.seekg(0, ios::beg); Point* Points = new Point[length]; for (size_t i = 0; i < length; i++) { fin.read((char*)&Points[i], sizeof(Point)); Points[i].Print(); } delete[] Points; fin.close(); getchar(); return 0; }
添加fin.exceptions(ifstream::badbit | ifstream::failbit)语句后,执行while (fin.read((char*)&tempPoint, sizeof(Point)))循环时会抛出以下异常:
An exception was raised at address 0x0000007FFECCC3CF19 in WorkWithFiles.exe: Microsoft C++: std::ios_base::failure exception at memory address 0x0000000059CBAFEF80. An unhandled exception occurred at address 0x0000007FFECCCCC3CF19 in WorkWithFiles.exe: Microsoft C++ exception: std::ios_base::failure at memory address 0x00000059CBAFEF80.
若不添加该语句,程序可正常运行,但第二个try/catch块无法捕获ifstream相关的异常。请问我哪里出错了?我正在练习使用try/catch处理ifstream/ofstream的文件打开异常,尝试过正确和错误的文件路径。
问题原因
你设置的异常掩码ifstream::badbit | ifstream::failbit会让ifstream在读取到文件末尾(EOF)且读取失败时抛出异常。当while (fin.read(...))循环执行完最后一个有效Point的读取后,下一次read尝试读取超出文件内容的部分,此时会触发failbit(因为无法读取足够的字节数),而你已经配置该位触发异常,但这段读取代码没有对应的catch块处理,就导致了未处理异常。
默认情况下,read操作到EOF时只会设置eofbit,不会触发failbit,但当读取的字节数不足要求的sizeof(Point)时(比如文件末尾剩下的字节不够一个Point),会同时设置eofbit和failbit——你的文件刚好是两个完整的Point,最后一次read成功后,下一次read直接触发failbit,进而抛出异常。
解决方案
方案1:调整异常掩码,仅捕获严重错误
如果你只需要捕获文件打开失败这类严重错误,不需要捕获读取到EOF的情况,可以修改异常掩码,只监听badbit(表示底层流出现不可恢复的错误):
fin.exceptions(ifstream::badbit);
这样既可以捕获文件打开时的严重异常,又不会在读取到EOF时抛出异常,循环可以正常终止。
方案2:给读取循环添加异常捕获
如果必须保留当前的异常掩码,需要给读取循环加上try/catch块,区分正常EOF和读取错误:
Point tempPoint; size_t length = 0; try { while (fin.read((char*)&tempPoint, sizeof(Point))) { length++; } } catch (const ifstream::failure& ex) { // 检查是否是EOF导致的异常,正常到达末尾则无需终止程序 if (!fin.eof()) { cout << ex.what() << endl; perror("Read file failed"); abort(); } }
方案3:提前计算文件大小,跳过循环计数
可以通过文件大小直接计算能读取的Point数量,避免循环读取到EOF:
fin.seekg(0, ios::end); size_t fileSize = fin.tellg(); size_t length = fileSize / sizeof(Point); fin.seekg(0, ios::beg); Point* Points = new Point[length]; for (size_t i = 0; i < length; i++) { fin.read((char*)&Points[i], sizeof(Point)); Points[i].Print(); } delete[] Points;
内容的提问来源于stack exchange,提问作者Alma Latina

