C++ getline() 和 boost::iostreams::filtering_istream 工作起来很奇怪

C++ getline() with boost::iostreams::filtering_istream works strange

提问人:snake_style 提问时间:8/14/2019 最后编辑:TriskalJMsnake_style 更新时间:8/22/2019 访问量:1687

问:

请帮助我了解以下程序行为差异的原因。

该程序从一个源和一个滤波器创建一个测试文本文件和一连串升压滤波器 ()。然后它尝试读取一些行。filtering_istream

#include <iostream>
#include <fstream>
#include <string>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/filtering_stream.hpp>


class my_filter : public boost::iostreams::input_filter
{
public:
    explicit my_filter(std::ostream& s) : m_out(s)
    {}

    template<typename Source>
    int get(Source& src)
    {
        int c = boost::iostreams::get(src);
        if(c == EOF || c == boost::iostreams::WOULD_BLOCK)
            return c;

        if(c == '\r')
            return boost::iostreams::WOULD_BLOCK;

        if(c == '\n')
        {
            m_out << m_str << std::endl;
            m_str = "";
        }
        else
        {
            m_str += c;
        }
        return c;
    }
private:
    std::ostream& m_out;
    std::string m_str;
};


int main()
{
    using namespace std;
    boost::iostreams::filtering_istream file;
    const std::string fname = "test.txt";

    std::ofstream f(fname, ios::out);
    f << "Hello\r\n";
    f << "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\r\n";
    f << "World!\r\n";
    f.close();

    file.push(my_filter(std::cout));
    file.push(boost::iostreams::file_descriptor(fname));

    std::string s;
    while(std::getline(file, s))
    {}
    return 0;
}

使用 clang 在线编译显示预期结果:

enter image description here

但是,如果我将字符串“111...111”(128 个)更改为 127 个字符串(255 个以此类推),结果会有所不同:

enter image description here

这种行为在我看来是不正确的。

注意:“111...111”(127 个)的长度与方法中的默认buffer_size相关......boost::iostreams::filtering_istream::push

file.push(my_filter(std::cout), default_buf_size=...)

您可以在此处查看并运行代码:code_example

更新 1

我觉得很奇怪,在某些情况下,返回值允许您进一步读取,而在其他情况下,它认为文件已完成。但根据文档:WOULD_BLOCK

WOULD_BLOCK - 表示输入暂时不可用

因此,它并不表示流的结束。

C++ C++11 IOSTREAM 提升-IOstreams

评论

0赞 CygnusX1 8/25/2019
不确定它将如何影响您的代码,但您是否尝试过使用二进制形式 () 而不是文本形式写入文件?在 Windows 下,在文本模式下操作时,已将“\n”替换为“\r\n”。因此,文件中的“\r”字符可能比预期的要多。在线编译器通常在下面使用 linux,其中唯一的“\n”(不带前面的“\r”)作为行尾,并且在文本模式下不会向文件添加任何额外的“\r”。ios::binarystd::ofstreamstd::ofstream

答:

3赞 Ionut 8/14/2019 #1

你想用这部分做什么?

if(c == '\r')
    return boost::iostreams::WOULD_BLOCK;

如果你试图忽略字符,那么你应该跳过它们并从源中读取另一个字符。Boost 文档有一个示例可以准确地说明这一点:\r

#include <ctype.h>                 // isalpha
#include <cstdio.h>                // EOF
#include <boost/iostreams/categories.hpp> // input_filter_tag
#include <boost/iostreams/operations.hpp> // get, WOULD_BLOCK

using namespace std;
using namespace boost::iostreams;

struct alphabetic_input_filter {
    typedef char              char_type;
    typedef input_filter_tag  category;

    template<typename Source>
    int get(Source& src)
    {
        int c;
        while ( (c = boost::iostreams::get(src)) != EOF &&
                 c != WOULD_BLOCK &&
                !isalpha((unsigned char) c) )
            ;
        return c;
    }
};

这将从源中删除所有非字母字符(请参阅 https://www.boost.org/doc/libs/1_68_0/libs/iostreams/doc/concepts/input_filter.html)。

现在,至于为什么您会看到上述行为,这基本上是正在发生的事情:

  • 您正好从缓冲区边界返回,在当前缓冲区中设置任何字符之前WOULD_BLOCKget()

  • 这是从如下所示的实现中调用的(请参阅末尾带有注释的两行):read()

template<>
struct read_filter_impl<any_tag> {
template<typename T, typename Source>
static std::streamsize read
   (T& t, Source& src, typename char_type_of<T>::type* s, std::streamsize n)
{   // gcc 2.95 needs namespace qualification for char_traits.
    typedef typename char_type_of<T>::type     char_type;
    typedef iostreams::char_traits<char_type>  traits_type;
    for (std::streamsize off = 0; off < n; ++off) {
        typename traits_type::int_type c = t.get(src);
        if (traits_type::is_eof(c))
            return check_eof(off);
        if (traits_type::would_block(c)) // It gets HERE
            return off; // and returns 0
        s[off] = traits_type::to_char_type(c);
    }
    return n;
}

(https://www.boost.org/doc/libs/1_70_0/boost/iostreams/read.hpp)

  • 这反过来又是从这样的代码中调用的:
// Read from source.
std::streamsize chars =
    obj().read(buf.data() + pback_size_, buf.size() - pback_size_, next_);
if (chars == -1) {
    this->set_true_eof(true);
    chars = 0;
}
setg(eback(), gptr(), buf.data() + pback_size_ + chars);
return chars != 0 ?
    traits_type::to_int_type(*gptr()) :
    traits_type::eof();

(https://www.boost.org/doc/libs/1_70_0/boost/iostreams/detail/streambuf/indirect_streambuf.hpp)

因此,由于它没有读取当前缓冲区中的任何字符,因此它会将其解释为文件结束并完全放弃。


更新

(太长了,无法发表评论)

您不是该函数的用户,该函数实际上是您的代码的用户。您的过滤器介于源和读取器之间。 确实表明输入暂时不可用,但最终由读者决定是否以及何时再次尝试。在这方面尽了最大的努力,它处理它设法从源头获得的任何内容,然后尝试再次读取(这就是为什么它不会在你第一次返回时停止,之后)。但是,如果源没有返回更多内容并且缓冲区是空的,它基本上别无选择,只能认为它到达了源的末尾。它在缓冲区中没有更多的字符要处理,并且无法从源中获取更多字符。WOULD_BLOCKstreambufWOULD_BLOCKHellostreambuf

如果你把两个连续的放在任何地方,你会看到相同的行为。例如,请尝试以下操作:\r

f << "Hello\r\r\n";
f << "nothing\r\n";
f << "World!\r\n";

请注意后面的两个 s。这与缓冲区大小无关。它只是从源读取,不会返回任何内容。\rHello

评论

0赞 snake_style 8/14/2019
谢谢@lonut!1) 它是遗留的 =( 2) 我看过 boost 的例子,但是: 3)我不明白为什么从filter::get()返回WOULD_BLOCK会导致在读取func时返回eof()?
0赞 Ionut 8/14/2019
WOULD_BLOCK停止从源代码读取,基本上返回它在当前迭代中已经读取的任何内容。因为这恰好发生在缓冲区边界上,所以它还没有读取任何内容,所以它返回 .不返回任何数据的读取被解释为文件结束。设置 eof 标志后,将来从流中读取将失败,因此它将完全停止。0
0赞 snake_style 8/14/2019
你写的是这些函数的逻辑——或多或少很清楚。但是,在某些条件下,WOULD_BLOCK 的返回值允许您进一步读取,而在其他情况下,它认为文件已完成,这难道不觉得奇怪吗?但是“WOULD_BLOCK - 表示输入暂时不可用”:它并不表示流的结束。作为此函数的用户,我不知道它的内部缓冲区及其边界......
0赞 Ionut 8/16/2019
@snake_style 我在答案中添加了更新,因为它太长了,无法发表评论。