Expected behavior when reading custom type from istream fails
Let's say I have a class with two data members:
class X {
std::string a;
int b;
public:
ostream& print(ostream& os);
istream& read(istream& is);
}
The function print
outputs all data well formatted, for example:
ostream& X::print(ostream& os) {
return os << a << ' ' << b;
}
Now the function read
should do the opposite: read something in the specified format (say how horse 54
, which will then result in a = "horse"
and b = 54
).
That is to say, the input does not fit this format. My intuition says that as soon as we encounter an unexpected character (like a letter while trying to read int
), we set failbit
and put every character we read so far into the stream. What is the expected behavior of the function read
(or operator>>
) in this case? How does the standard library work? Should we need to undo all character extraction if the read fails?
source to share
In general, in the event of a failure, you expect any object to be invoked in a valid state. Whether it refers to rolling back from the variable being written, partially writing it, or something else depends on what you are trying to accomplish and what you think is best for the user. In any case, be sure to document your choices so that users can program knowing how your implementation will behave.
As Anton already said, the standard library doesn't seem to make any effort to translate characters into a stream. Another practical example that seems to be closer to what you are doing is operator>>
for a class std::complex
that actually needs to read multiple tokens in sequence before they are executed.
template<typename _Tp, typename _CharT, class _Traits>
basic_istream<_CharT, _Traits>&
operator>>(basic_istream<_CharT, _Traits>& __is, complex<_Tp>& __x)
{
_Tp __re_x, __im_x;
_CharT __ch;
__is >> __ch;
if (__ch == '(')
{
__is >> __re_x >> __ch;
if (__ch == ',')
{
__is >> __im_x >> __ch;
if (__ch == ')')
__x = complex<_Tp>(__re_x, __im_x);
else
__is.setstate(ios_base::failbit);
}
else if (__ch == ')')
__x = __re_x;
else
__is.setstate(ios_base::failbit);
}
else
{
__is.putback(__ch);
__is >> __re_x;
__x = __re_x;
}
return __is;
}
source to share
The standard library never tries to put characters on the stream on error. For example, here's an excerpt from the standard on how to short
read from stream ( 27.7.2.2.2/2
):
operator>>(short& val);
The conversion happens as if it were done by the following piece of code (using the same notation as for the previous piece of code):
typedef num_get<charT,istreambuf_iterator<charT,traits> > numget;
iostate err = ios_base::goodbit;
long lval;
use_facet<numget>(loc).get(*this, 0, *this, err, lval);
if (lval < numeric_limits<short>::min()) {
err |= ios_base::failbit;
val = numeric_limits<short>::min();
} else if (numeric_limits<short>::max() < lval) {
err |= ios_base::failbit;
val = numeric_limits<short>::max();
} else
val = static_cast<short>(lval);
setstate(err);
You can see that even if the number does not fit in short
, nothing is returned.
source to share