我有一个代表字符序列的类,我想为它实现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 ++中成功所以我可以将每个有效前缀视为有效输入吗?
(顺便说一句,这对于显式循环来说相当简单,但我试图避免使用显式循环来支持算法。)
答案 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