istreambuf_iterator何时抛出异常?

时间:2014-10-07 08:34:31

标签: c++ gcc c++11

istreambuf_iterator何时抛出异常?当底层流尝试读取目录时,我收到异常,但在其他情况下则没有。具体来说,我从Jan-Philip Gehrcke的blog中抓取了脚本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 "validline\ninvalidline" > $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在从目录中读取时不会引发异常。相反,它设置了badbit。为什么代码会在一种情况下抛出异常并在另一种情况下设置badbit?

编辑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只设置badbit并且不会抛出异常。

最重要的是,尝试从目录中读取是不好的。是否有一个好的stl(不是提升)方式来检测这个?还有其他情况会抛出需要捕获的特定于实现的异常吗?

1 个答案:

答案 0 :(得分:1)

istreambuf_iterator本身永远不会抛出。

它迭代的basic_filebuf打开文件就好像通过调用std::fopen一样(无论它实际上是否这样做都无关紧要)。

正如您在this question中看到的那样,标准C没有关于目录是什么的概念,因此尝试打开一个是实现定义或未定义的行为。在gcc的情况下(或者更确切地说是libc),可以打开目录,但是你可以用它做很多事情。

根据标准,basic_filebuf也永远不会抛出,但由于你已经超越了标准强制要求,试图打开不是文件的东西,libstdc ++是免费的当您尝试从目录中读取时抛出异常。

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


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

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