insertion, extraction, and streams

C

Christopher

I tryed an example of how to do streams "the right way" based on an example
from the book "Standard C++ IOStreams and Locals" by Langer and Kreft. My
example doesn't seem to handle errors correctly. I am unsure if the problem
is arising from the << and >> operators for my user defined type, or the
handling of std::cin itself. If I uncomment the ignore statement,
std::cin::eek:perator >> never seems to return. Otherwise it just goes into an
infinite loop. Any advise?

source:

/**
* This program demonstrates the _proper_ way to provide insterters and
* extractors for a user defined type, based on the book
* "Standard C++ IOStreams and Locales" by Langer and Kreft
*
* @author Christopher Pisz
*/

#include <ctime>
#include <locale>
#include <limits>
#include <iostream>

//------------------------------------------------------------------------------
/**
* A very simplified date class
*
* Intended to demonstrate proper insertion and extraction operators for user
* defined types
*/
class Date
{
public:

Date(int day, int month, int year)
{
m_date.tm_mday = day;
m_date.tm_mon = month - 1;
m_date.tm_year = year - 1900;
}

private:

template<class charT, class Traits>
friend std::basic_istream<charT, Traits> &
operator >>(std::basic_istream<charT, Traits> & is, Date & date);

template<class charT, class Traits>
friend std::basic_ostream<charT, Traits> &
operator <<(std::basic_ostream<charT, Traits> & os, const Date &
date);

/** Structure from standard <ctime> header which contains date data */
tm m_date;
};

//------------------------------------------------------------------------------
/**
* The insertion and extraction methods for the Date class
*
* NOTE - Using basic_istream and basic_ostream opposed to the commonly
misused
* istream or ostream allows for differant char types and traits to be
* supported along with facets using those template arguments
*
* NOTE - You will not see the already built in << or >> operations being
* performed for each primitive data member, because it is inefficient
* in that it would call flush() and attempt to synchronize cin and
cout
* each time they were used
*
* TODO - The insertion and extraction methods should also check the
exception
* mask of the stream and check for and rethrow the approproate
* exceptions
*
* TODO - Stream prefix and suffix operations were also discussed but skipped
in
* this implementation
*/
template<class charT, class Traits>
std::basic_istream<charT, Traits> & operator >>(std::basic_istream<charT,
Traits> & is, Date & date)
{
// Check if the stream is in a bad state
if( !is.good() )
{
return is;
}

// Use the time_get facet, which provides parsing and formatting

/** Used to get error state from the call to the time_get facet */
std::ios_base::iostate err = std::ios_base::goodbit;

std::use_facet<std::time_get<charT, std::istreambuf_iterator<charT,
Traits> > >
(is.getloc()).get_date(is, std::istreambuf_iterator<charT,Traits>(),
is, err, &date.m_date);

// Set the stream state to the state of the facet to inidicate any errors
is.setstate(err);

return is;
}

template<class charT, class Traits>
std::basic_ostream<charT, Traits> & operator <<(std::basic_ostream<charT,
Traits> & os, const Date & date)
{
// Check if the stream is in a bad state
if( !os.good() )
{
return os;
}

// Use the time_put facet, which provides parsing and formatting
if( std::use_facet<std::time_put<charT, std::eek:streambuf_iterator<charT,
Traits> > >
(os.getloc()).put(os, os, os.fill(), &date.m_date, 'x').failed() )
{
// Unrecoverable error
//
// Note - It could not have been formatting error, since a valid Date
// object was passed in as a parameter
os.setstate(std::ios_base::badbit);
}

return os;
}

//------------------------------------------------------------------------------
int main()
{
Date date(07, 07, 1976);

std::cout << "Enter your birthdate (<day> <month> <4 digit year>): ";

while( std::cin >> date, !std::cin.good() )
{
// Check if there was a fatal error
if( std::cin.bad() )
{
std::cout << "\nFatal Error, aborting...\n" << std::endl;

system("pause");
return 1;
}

// Otherwise, there was a recoverable error
// Either std::ios_base::failbit or std::ios_base::eofbit was set
std::cout << "\nError getting input. Try format of: \n"
<< "<2 digit day> <2 digit month> <4 digit year>\n"
<< std::endl;

// Reset error bits
std::cin.clear();

/* Infinite loop detectable here */

/* If I uncomment this then the getting of input never stops when
enter is hit
*
// Empty any left over characters from the stream
std::cin.ignore(std::numeric_limits<std::streamsize>::max());
*/
}

// Successfully got input
std::cout << "\nYou said you were born: "
<< date << "\n" << std::endl;

system("pause");
return 0;
}
 
A

Alf P. Steinbach

* Christopher:
I tryed an example of how to do streams "the right way" based on an
example from the book "Standard C++ IOStreams and Locals" by Langer and
Kreft. My example doesn't seem to handle errors correctly. I am unsure
if the problem is arising from the << and >> operators for my user
defined type, or the handling of std::cin itself.

The statement below is difficult for me to understand:

If I uncomment the
ignore statement, std::cin::eek:perator >> never seems to return. Otherwise
it just goes into an infinite loop.

"never seems to return" is to my mind about the same as "goes into an
infinite loop", yet, from the word "Otherwise", these two possibilities
seem to be regarded as mutually exclusive alternatives?


Any advise? [snip]


// Reset error bits
std::cin.clear();

/* Infinite loop detectable here */

/* If I uncomment this then the getting of input never stops when
enter is hit
*
// Empty any left over characters from the stream
std::cin.ignore(std::numeric_limits<std::streamsize>::max());
*/

Make that

std::cin.ignore( INT_MAX, '\n');

Otherwise is ignoring until end-of-file (in Windows, until you type Ctrl
Z on an empty line, or with redirected input, until end-of-file).

Be aware though, that when I made this change, your current program
simply ignored end-of-file, and kept asking for a valid date until one
was entered. That's surely not the behavior you want.

Cheers, & hth.,

- Alf


PS: Keeping the 'main' function as-is, the earlier stuff might be recoded as

#include <ctime>
#include <locale>
#include <limits>
#include <iostream>

//--------------------------------------------------------------------------
// A very simplified date class:

class Date
{
private:
std::tm myComponents;

public:
Date(int day, int month, int year)
{
myComponents.tm_mday = day;
myComponents.tm_mon = month - 1;
myComponents.tm_year = year - 1900;
}

Date( std::tm const& components )
: myComponents( components )
{}

std::tm components() const { return myComponents; }
};


//--------------------------------------------------------------------------
// The insertion and extraction methods for the Date class

template<class CharT, class Traits>
std::basic_istream<CharT, Traits> & operator>>(
std::basic_istream<CharT, Traits>& stream,
Date& date
)
{
typedef std::istreambuf_iterator<CharT, Traits> InputIterator;
typedef std::time_get<CharT, InputIterator > TimeFacet;

if( !stream.good() ) { return stream; }

TimeFacet const& timeFacet =
std::use_facet<TimeFacet>( stream.getloc() );

std::tm dateComponents;
std::ios_base::iostate state = std::ios_base::goodbit;

timeFacet.get_date(
stream,
InputIterator(),
stream,
state,
&dateComponents
);
stream.setstate( state );
if( stream.good() ) { date = Date( dateComponents ); }

return stream;
}

template<class CharT, class Traits>
std::basic_ostream<CharT, Traits>& operator<<(
std::basic_ostream<CharT, Traits>& stream,
Date const& date
)
{
typedef std::eek:streambuf_iterator<CharT, Traits> OutputIterator;
typedef std::time_put<CharT, OutputIterator > TimeFacet;

if( !stream.good() ) { return stream; }

TimeFacet const& timeFacet =
std::use_facet<TimeFacet>( stream.getloc() );
std::tm const dateComponents = date.components();

if( timeFacet.put( stream, stream, stream.fill(), &dateComponents,
'x' ).failed() )
{
stream.setstate( std::ios_base::badbit );
}

return stream;
}

Since I hate iostreams I don't know how correct that is: I simply
transformed your code in order to remove Date's direct dependency on
iostreams, and to name things.

However, when striving for correctness, as seems to be your goal, it's
generally a good idea to decouple things and to name things, for clarity.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top