1

我有一个代表字符序列的类,我想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");

特别是,一旦我们到达“<code>GATTACA FOO”中的空间,复制停止(预期)并将故障位设置为istream(也是预期的)。但是,就目前而言,读取操作实际上是成功的seq

我可以完全使用它来建模std::copy吗?我也想过使用 an istreambuf_iterator,但这实际上并不能解决这个特定问题。

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

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

4

2 回答 2

5

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

要使用迭代器和算法做到这一点,您需要类似的东西

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如果没有提取字符:

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

{}最终版本还使用了我最喜欢的 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;
}

哨兵将跳过前导空格,如果您到达输入的末尾,它将设置eofbit。可能应该进行的另一个更改是seq在将任何内容推入之前清空,例如,从您的类型开始rhs.clear()或等效。seq

于 2013-01-31T11:39:36.477 回答
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

于 2013-01-31T10:54:13.537 回答