通过副本实现从流中读取

时间:2013-01-31 09:50:24

标签: c++ io istream istream-iterator

我有一个代表字符序列的类,我想为它实现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;
}

但是,以下情况可能会失败:

std::istringstream istr("GATTACA FOO");
seq s;
assert((istr >> s) and s == "GATTACA");

特别是,一旦我们到达“GATTACA FOO”中的空格,复制就会停止(预期)并在istream上设置failbit(也是预期的)。但是,就seq而言,读取操作实际上已成功。

我可以使用std::copy对此进行建模吗?我还想过使用istreambuf_iterator,但实际上并没有解决这个特殊问题。

此外,对输入“GATTACAFOO的读取操作应该失败,因为该输入不代表有效的DNA序列(这是我的班级所代表的)。另一方面,从输入int中读取42foo实际上在C ++中成功所以我可以将每个有效前缀视为有效输入吗?

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

2 个答案:

答案 0 :(得分:5)

您不希望clear(eofbit),因为如果由于达到EOF而导致阅读失败,failbit应保持设置。否则,如果您将eofbit设置为不failbit,则while (in >> s)之类的循环将在达到EOF后尝试另一次读取,然后 读取将设置{{ 1}}再次。除非它正在使用您的failbit,否则它会清除它,并尝试再次阅读。然后再次。然后再次。如果由于EOF读取失败,则流的正确行为是设置operator>>,所以只需将其设置为。

要使用迭代器和算法执行此操作,您需要类似

的内容
failbit

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

copy_while(InputIter, InputIter, OutputIter, Pred);

现在你可以这样使用:

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;
  }

这样可行,但问题是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"); } 使用istream_iterator来读取字符,因此会跳过空格。这意味着算法会占用operator>>之后的空格并将其丢弃,因此将此值添加到"GATTACA"的末尾将失败:

main

要解决此问题,请使用assert(istr.get() == ' '); 来跳过空格:

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; }

seq

最终版本也使用我最喜欢的C ++ 11技巧之一,通过使用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; } 作为结束迭代器来稍微简化它。 {}的第二个参数的类型必须与第一个参数的类型相同,后者推导为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; } 。应该做的另一个改变是在将eofbit推入其中之前清空seq,例如以rhs.clear()类型开头seq或等效。

答案 1 :(得分:2)

  

特别是,一旦我们到达“GATTACA FOO”的空间,就会复制   停止(预期)

这个假设已经错了。相反,你没有得到

std::istringstream istr("GATTACA FOO");
seq s;
assert(!(istr >> s) && s == "GATTACAFOO");

使用istream_iterator<char>和标准copy算法进行复制不起作用,因为这将始终提取字符直到流结束。

如果达到结束条件且结束条件不得提取不匹配的字符(即使用in.peek()或甚至直接查看streambuf),则需要一个过早终止的副本。

使用std::copy()执行此操作需要您自己的特殊用途流迭代器(比较等于结束迭代器,如果终止条件与下一个字符匹配。恕我直言,创建比明确循环更多的默默无闻。 YMMV