通过副本实现从流中读取

Kon*_*lph 1 c++ io istream-iterator istream

我有一个表示字符序列的类,我想operator >>为它实现一个.我的实现目前看起来像这样:

inline std::istream& operator >>(std::istream& in, seq& rhs) {
    std::copy(
        std::istream_iterator<char>(in),
        std::istream_iterator<char>(),
        std::back_inserter(rhs));
    // `copy` doesn't know when to stop reading so it always also sets `fail`
    // along with `eof`, even if reading succeeded. On the other hand, when
    // reading actually failed, `eof` is not going to be set.
    if (in.fail() and in.eof())
        in.clear(std::ios_base::eofbit);
    return in;
}
Run Code Online (Sandbox Code Playgroud)

但是,以下可预测的失败:

std::istringstream istr("GATTACA FOO");
seq s;
assert((istr >> s) and s == "GATTACA");
Run Code Online (Sandbox Code Playgroud)

特别是,一旦我们到达" GATTACA FOO"中的空格,复制停止(预期)并将故障位置设置为istream(也是预期的).但是,就所seq涉及的而言,读取操作实际上是成功的.

我可以使用这种模型std::copy吗?我也想过用一个istreambuf_iterator而不是这个,但实际上并没有解决这个问题.

更重要的是,对输入" GATTACAFOO" 的读取操作应该失败,因为该输入不代表有效的DNA序列(这是我的类所代表的).另一方面,int从输入读取42foo实际上在C++中成功,所以也许我应该将每个有效前缀视为有效输入?

(顺便说一句,这对于显式循环来说相当简单,但我试图避免使用显式循环来支持算法.)

Jon*_*ely 5

您不想这样,clear(eofbit)因为failbit如果由于达到EOF而读取失败,则应保持设置.否则,如果您只是离开eofbitset而没有failbit循环,例如while (in >> s)在到达EOF后将尝试另一次读取,然后读取将failbit再次设置.除非它正在使用operator>>它,否则将清除它,并尝试再次阅读.然后再次.然后再次.流的正确行为是设置failbit是否由于EOF而读取失败,所以只需将其保留即可.

要使用迭代器和算法,你需要这样的东西

copy_while(InputIter, InputIter, OutputIter, Pred);
Run Code Online (Sandbox Code Playgroud)

只有在谓词为真时才复制输入序列,但标准库中不存在.你当然可以写一个.

template<typename InputIter, typename OutputIter, typename Pred>
  OutputIter
  copy_while(InputIter begin, InputIter end, OutputIter result, Pred pred)
  {
    while (begin != end)
    {
      typename std::iterator_traits<InputIter>::value_type value = *begin;
      if (!pred(value))
        break;
      *result = value;
      result++;
      begin++;
    }
    return result;
  }
Run Code Online (Sandbox Code Playgroud)

现在你可以像这样使用它:

inline bool
is_valid_seq_char(char c)
{ return std::string("ACGT").find(c) != std::string::npos; }

inline std::istream&
operator>>(std::istream& in, seq& rhs)
{
    copy_while(
        std::istream_iterator<char>(in),
        std::istream_iterator<char>(),
        std::back_inserter(rhs),
        &is_valid_seq_char);
    return in;
}

int main()
{
    std::istringstream istr("GATTACA FOO");
    seq s;
    assert((istr >> s) and s == "GATTACA");
}
Run Code Online (Sandbox Code Playgroud)

这工作,但问题是,istream_iterator使用operator>>读取字符,所以它跳过空白.这意味着"GATTACA"算法会消耗后续空间并将其丢弃,因此将其添加到末尾main会失败:

assert(istr.get() == ' ');
Run Code Online (Sandbox Code Playgroud)

要解决这个istreambuf_iterator不跳过空格的用法:

inline std::istream&
operator>>(std::istream& in, seq& rhs)
{
    copy_while(
        std::istreambuf_iterator<char>(in),
        std::istreambuf_iterator<char>(),
        std::back_inserter(rhs),
        &is_valid_seq_char);
    return in;
}
Run Code Online (Sandbox Code Playgroud)

要完成此操作,您可能希望指示无法提取seq如果没有提取的字符:

inline std::istream&
operator>>(std::istream& in, seq& rhs)
{
    copy_while( std::istreambuf_iterator<char>(in), {},
        std::back_inserter(rhs), &is_valid_seq_char);
    if (seq.empty())
      in.setstate(std::ios::failbit);  // no seq in stream
    return in;
}
Run Code Online (Sandbox Code Playgroud)

最终版本还使用了我最喜欢的C++ 11技巧之一,通过使用{}最终迭代器来稍微简化它.第二个参数的类型copy_while必须与第一个参数的类型相同,后者推导为std::istreambuf_iterator<char>,因此{}简单地初始化相同类型的另一个迭代器.

编辑:如果你想要std::string提取更接近匹配,那么你也可以这样做:

inline std::istream&
operator>>(std::istream& in, seq& rhs)
{
    std::istream::sentry s(in);
    if (s)
    {
        copy_while( std::istreambuf_iterator<char>(in), {},
                    std::back_inserter(rhs), &is_valid_seq_char);
        int eof = std::char_traits<char>::eof();
        if (std::char_traits<char>::eq_int_type(in.rdbuf()->sgetc(), eof))
            in.setstate(std::ios::eofbit);
    }
    if (rhs.empty())
        in.setstate(std::ios::failbit);
    return in;
}
Run Code Online (Sandbox Code Playgroud)

哨兵将跳过前导空格,如果到达输入的末尾,它将设置eofbit.应该做的另一个改变是seq在将任何东西推入其中之前清空它,例如从rhs.clear()你的seq类型开始或等效.