【问题标题】:C++ getline() with boost::iostreams::filtering_istream works strangeC++ getline() 与 boost::iostreams::filtering_istream 工作奇怪
【发布时间】:2019-12-20 19:18:48
【问题描述】:

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

程序从一个源和一个过滤器创建一个测试文本文件和一串增强过滤器 (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在线编译显示预期结果:

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

我觉得这种行为不正确。

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

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

您可以在这里查看和运行代码:code_example

更新 1

奇怪的是,在某些情况下,WOULD_BLOCK 的返回值可以让你进一步阅读,而在其他情况下,它认为文件已完成。但根据文档:

WOULD_BLOCK - 表示输入暂时不可用

所以它并不表示流的结束。

【问题讨论】:

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

标签: c++ c++11 boost iostream boost-iostreams


【解决方案1】:

你想用这部分做什么?

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

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

#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)。

现在至于为什么你会看到上面的行为,这就是基本上发生的事情:

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

  • 这是从一个看起来像这样的read() 实现调用的(参见最后带有 cmets 的两行):

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_BLOCK 确实表明输入暂时不可用,但最终是否以及何时再次尝试由读者决定。 streambuf 在这方面尽其所能,它处理它设法从源获得的任何内容,然后尝试再次读取(这就是为什么它在您第一次返回 WOULD_BLOCK 时不会停止,在 Hello 之后)。但是如果source没有返回任何东西并且streambuf缓冲区是空的,它基本上别无选择,只能认为它到达了source的末尾。它在缓冲区中没有要处理的字符,也无法从源中获取更多字符。

如果您将两个连续的\r 放在任何地方,您将看到相同的行为。试试这个例子:

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

注意Hello 后面的两个\rs。这与缓冲区大小无关。这只是从不返回任何内容的源中读取。

【讨论】:

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