istreambuf_iterator何时引发异常

When does an istreambuf_iterator throw an exception?

本文关键字:异常 何时引 iterator istreambuf      更新时间:2023-10-16

istreambuf_iterator何时引发异常? 当基础流尝试读取目录时,我收到异常,但在其他情况下没有。 具体来说,我从Jan-Philip Gehrcke的博客中抓取了脚本readfile_tests.sh,并对其进行了轻微修改:

$ cat readfile_tests.sh 
#!/bin/bash
COMPILATION_SOURCE=$1
NE_FILE="na"
EMPTY_FILE="empty_file"
ONE_LINE_FILE="one_line_file"
INVALID_LINE_FILE="invalid_line_file"
FILE_READ="file_read"
FILE_WRITTEN="file_written"
FILE_DENIED="/root/.bashrc"
DIR="dir"
# compile test program, resulting in a.out executable
g++ -std=c++11 $COMPILATION_SOURCE
# create test files / directories and put them in the desired state
touch $EMPTY_FILE
if [[ ! -d $DIR ]]; then
    mkdir $DIR
fi
echo "rofl" > $ONE_LINE_FILE
echo -ne "validlineninvalidline" > $INVALID_LINE_FILE
echo "i am opened to read from" > $FILE_READ
python -c 'import time; f = open("'$FILE_READ'"); time.sleep(4)' &
echo "i am opened to write to" > $FILE_WRITTEN
python -c 'import time; f = open("'$FILE_WRITTEN'", "a"); time.sleep(4)' & 
# execute test cases
echo "******** testing on non-existent file.."
./a.out $NE_FILE
echo
echo "******** testing on empty file.."
./a.out $EMPTY_FILE
echo
echo "******** testing on valid file with one line content"
./a.out $ONE_LINE_FILE
echo
echo "******** testing on a file with one valid and one invalid line"
./a.out $INVALID_LINE_FILE
echo
echo "******** testing on a file that is read by another process"
./a.out $FILE_READ
echo
echo "******** testing on a file that is written to by another process"
./a.out $FILE_WRITTEN
echo
echo "******** testing on a /root/.bashrc (access should be denied)"
./a.out $FILE_DENIED
echo
echo "******** testing on a directory"
./a.out $DIR

然后,我在以下程序上运行了测试

$  cat test.cpp
#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>
#include <string>
int main(int argc, char* argv[]) {
    if(argc != 2) {
        std::cerr << "Provide one argument" << std::endl;
        return EXIT_FAILURE;
    }
    std::ifstream fin(argv[1]);
    std::istreambuf_iterator <char> eos;
    std::istreambuf_iterator <char> fin_it(fin.rdbuf());
    std::string str;
    try {
        std::copy(fin_it,eos,std::back_inserter(str));
    } catch(...) {
        perror("Error");
        throw;
    }
    std::cout << str;
    return EXIT_SUCCESS;
}

运行它后,我收到了以下结果:

$ ./readfile_tests.sh test.cpp
******** testing on non-existent file..
******** testing on empty file..
******** testing on valid file with one line content
rofl
******** testing on a file with one valid and one invalid line
validline
invalidline
******** testing on a file that is read by another process
i am opened to read from
******** testing on a file that is written to by another process
i am opened to write to
******** testing on a /root/.bashrc (access should be denied)
******** testing on a directory
Error: Is a directory
terminate called after throwing an instance of 'std::ios_base::failure'
  what():  basic_filebuf::underflow error reading the file
./readfile_tests.sh: line 51: 22563 Aborted                 ./a.out $DIR

这些结果对我来说很奇怪,因为在读取目录测试时抛出了异常,但在尝试读取/root/.bashrc时没有抛出异常。 因此,istreambuf_iterator何时抛出异常? 万一重要,我使用gcc version 4.7.3.

编辑 1

是的,打开目录进行阅读是不标准的和不好的。 同时,我需要正确捕获此案。 根据下面的注释,这是一段尝试从目录中读取的代码:

#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>
#include <string>
void withIterators() {
    std::ifstream fin("mydirectory");
    if(!fin.is_open())
        perror("Error opening file");
    std::istreambuf_iterator <char> eos;
    std::istreambuf_iterator <char> fin_it(fin.rdbuf());
    std::string str;
    try {
        std::copy(fin_it,eos,std::back_inserter(str));
    } catch(...) {
        perror("Error reading file");
    }
}
void noIterators() {
    std::ifstream fin("mydirectory");
    if(!fin.is_open())
        perror("Error opening file");
    std::string line;
    while(getline(fin,line)) {}
    if(fin.bad())
        perror("Error reading file");
}
int main() {
    withIterators();
    noIterators();
}

运行后,我们收到输出:

Error reading file: Is a directory
Error reading file: Is a directory

这告诉我们两件事。 首先,我们无法抓住这样一个事实,即我们在 fin 的构造函数之后将目录作为文件打开。 其次,仅在使用迭代器时引发异常。 上面noIterators的代码在从目录中读取时不会引发异常。 相反,它设置了坏位。 为什么代码会在一种情况下抛出异常并在另一种情况下设置坏位?

编辑 2

我从上面扩展了目录打开代码,以便更好地跟踪正在发生的事情

#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>
#include <string>
void withIterators() {
    std::ifstream fin("mydirectory");
    if(!fin.is_open())
        perror("Error opening file");
    std::istreambuf_iterator <char> eos;
    std::istreambuf_iterator <char> fin_it(fin.rdbuf());
    std::string str;
    try {
        std::copy(fin_it,eos,std::back_inserter(str));
    } catch(...) {
        perror("Error reading file");
    }
}
void withIteratorsUnderflow() {
    std::ifstream fin("mydirectory");
    try {
        fin.rdbuf()->sgetc();
    } catch(...) {
        perror("Error reading file");
    }
}
void withOtherIterators() {
    std::ifstream fin("mydirectory");
    if(!fin.is_open())
        perror("Error opening file");
    std::istream_iterator <char> eos;
    try {
        std::istream_iterator <char> fin_it(fin);
    } catch(...) {
        perror("Error making iterator");
    }
}
void noIterators() {
    std::ifstream fin("mydirectory");
    if(!fin.is_open())
        perror("Error opening file");
    std::string line;
    while(getline(fin,line)) {}
    if(fin.bad())
        perror("Error reading file");
}
int main() {
    withIterators();
    withIteratorsUnderflow();
    withOtherIterators();
    noIterators();
}

基本上,我们在不同的地方得到例外,或者没有,但出于类似的原因。

withIterators(),对copy的调用最终调用代码/usr/lib/gcc/i686-pc-linux-gnu/4.7.3/include/g++-v4/bits/streambuf_iterator.h:187,它声明

else if (!traits_type::eq_int_type((__ret = _M_sbuf->sgetc())

sgetc()的调用会引发异常。 很可能,这是按照@user657267的建议调用下溢。

withIteratorsUnderflow(),我们看到异常被sgetc直接抛出,这证实了withIterators()会发生什么。

withOtherIterators(),我们在创建std::istream_iterator后立即抛出异常。 这与 std::istreambuf_iterator 发生的情况不同,后者直到后来才抛出异常。 无论如何,在构造过程中,我们将代码称为 /usr/lib/gcc/i686-pc-linux-gnu/4.7.3/include/g++-v4/bits/stream_iterator.h:121 ,它声明

*_M_stream >> _M_value;

基本上,类istream_iterator创建一个构造istream,该立即尝试读取字符,从而引发异常。

noIterators() 上,getline 只是设置坏位,不会抛出异常。

底线是尝试从目录中读取是不好的。 有没有一个好的stl(不是boost(方法来检测这一点? 是否有其他情况会引发需要捕获的特定于实现的异常?

istreambuf_iterator本身永远不会抛出。

它迭代basic_filebuf会像调用std::fopen一样打开文件(无论它是否真的这样做都无关紧要(。

正如你在这个问题中看到的,标准 C 没有目录的概念,所以尝试打开一个目录要么是实现定义的行为,要么是未定义的行为。在 gcc 的情况下(或者更确切地说是 libc(,目录可以打开,但你不能用它做太多事情。

根据标准,basic_filebuf也永远不会抛出,但由于您已经通过尝试打开不是文件的内容超出了标准强制执行的范围,因此当您尝试从目录中读取时,libstdc++ 可以自由抛出异常。

检查给定名称是否为文件通常取决于平台,但boost::filesystem/std::experimental::filesystem提供便携式检查。


要回答您的编辑,尝试从流中读取时不会看到异常,因为哨兵将失败,并且流永远不会从缓冲区读取(最终调用underflow(。 istreambuf_iterator直接在缓冲区上运行,因此没有哨兵。

事实证明,libstdc++不会对overflow进行类似的调用,并且由于underflow的设置方式,如果流打开进行写入并且overflow返回eof,则不会抛出异常。