- Using Manipulators To Control Object Format
- Connecting Algorithms and Containers to Streams
- Summary
Connecting Algorithms and Containers to Streams
To illustrate how all of this works in C++, we'll use our course class and Schedule container from part 1 and part 2 of this series. We want our course class to be iostream compatible. Keep in mind that the built-in types can be streamed with different formatting depending on the application. For instance, a float can be formatted in the stream with fixed-point, scientific, or exponential notation, and can have a specified precision. An int can be formatted in the stream with hex, oct, or decimal format; a string can be formatted in the stream with left/right alignment, specified padding, and fill character. The user-defined classes should also come prepared. In our case, the course class has its native format. It can also be represented as HTML, XML, or horn clause. We also would like manipulators that can be used to specify the format our course class can take during insertion or extraction. We have several user-defined manipulators that apply to our course class:
XML
HTML
hornClause
militaryTime
clockTime
SSPR (short for simple sound phonetic representation)
Listing 1 shows an abbreviated declaration and definition for our course class, the definition of operators << and >>, manipulators, and our schedule of courses.
Listing 1 An abbreviated declaration and definition of the course class.
class course{ string StartTime; string EndTime; string Days; string Description; ... public: course(void); string startTime(void) const; string endTime(void) const; string days(void) const; string description(void) const; ... course &operator=(const course &Course); friend istream &operator>>(istream &In,course &Course); friend ostream &operator<<(ostream &Out,const course &Course); }; // ... // definition of operator<<() ostream &operator<<(ostream &Out,const course &Course) { if(Out.iword(UserFlag["LanguageBase"]) == XmlRep){ Out << "<COURSE_START_TIME>" << Course.startTime() << "</COURSE_START_TIME>" << "<COURSE_END_TIME>" << Course.endTime() << "</COURSE_END_TIME>" << "<COURSE_DESCRIPTION>" << Course.description() << "</COURSE_DESCRIPTION>" << "<COURSE_DAYS>" << Course.days() << "</COURSE_DAYS>"; } else if(Out.iword(UserFlag["LanguageBase"]) == HtmlRep){ Out << "<tr>" << endl << "<td>Start Time</td>" << endl << "<td>" << Course.startTime() << "</td></tr>" << endl << "<tr>" << endl << "<td>End Time</td>" << endl << "<td>" << Course.endTime() << "</td></tr>" << endl << "<tr>" << endl << "<td>Description</td>"<< endl << "<td>" << Course.description()<< "</td></tr>"<< endl << "<tr>" << endl << "<td>Days</td>" << endl << "<td>" << Course.days() << "</td></tr>" << endl; } else if(Out.iword(UserFlag["HornClause"]) == HClause){ Out << Course.toHornClause() // Horn Clause processing } else{ Out << Course.startTime() << endl << Course.endTime() << endl << Course.description() << endl << Course.days() << endl; } return(Out); } // definition of operator>>() istream &operator>>(istream &In, course &Course) { string X; int Pos; char Temp[80]; In.getline(Temp,80,'>'); X.assign(Temp); Pos = X.find("COURSE"); if(Pos != string::npos){ In.getline(Temp,80,'<'); Course.StartTime.assign(Temp); In.getline(Temp,80,'>'); In.getline(Temp,80,'>'); In.getline(Temp,80,'<'); Course.EndTime.assign(Temp); In.getline(Temp,80,'>'); In.getline(Temp,80,'>'); In.getline(Temp,80,'<'); Course.Description.assign(Temp); In.getline(Temp,80,'>'); In.getline(Temp,80,'>'); In.getline(Temp,80,'<'); Course.Days.assign(Temp); In.getline(Temp,80,'\n'); } else{ In.getline(Temp,80,'\n'); } return(In); } // manipulators ostream &xml(ostream &SomeStream) { if(UserFlag.find("LanguageBase") == UserFlag.end()){ UserFlag["LanguageBase"] = SomeStream.xalloc(); } SomeStream.iword(UserFlag["LanguageBase"]) = XmlRep; return(SomeStream); } ostream &html(ostream &SomeStream) { if(UserFlag.find("LanguageBase") == UserFlag.end()){ UserFlag["LanguageBase"] = SomeStream.xalloc(); } SomeStream.iword(UserFlag["LanguageBase"]) = HtmlRep; return(SomeStream); } ostream &hornClause(ostream &SomeStream) { if(UserFlag.find("LanguageBase") == UserFlag.end()){ UserFlag["LanguageBase"] = SomeStream.xalloc(); } SomeStream.iword(UserFlag["LanguageBase"]) = HClause; return(SomeStream); } // declaration of schedule of courses vector<course> Schedule;
We want to send our schedule of courses to a printer. Rather than send the courses in their native format, we would like to insert the courses as XML into the printer stream. We'll avoid using the traditional print loop and save coding effort by using the STL copy() algorithm instead. Listing 2 shows how this is set up using an ostream_iterator and the Schedule container.
Listing 2 Using the copy algorithm and iterators to read and print a container of course objects.
... int main(int argc,char *argv[]) { ifstream Input(open("courses.dat")); vector<course> Schedule; istream_iterator<course> In(Input); istream_iterator<course> eof; copy(In,eof,back_inserter(Schedule)); Input.close(); ... ofstream DegreePlan(open("/dev/lp0",O_WRONLY)); ... DegreePlan << xml; ostream_iterator<course> Out(DegreePlan,"\n"); copy(Schedule.begin(),Schedule.end(),Out); ... }
In Listing 2, the contents for the course objects are stored in the courses.dat file in XML format. The copy algorithm uses the istream_iterator that in turn utilizes the >> operator of the course object. Listing 1 shows the definition of the extractor. The copy algorithm assigns the content of the course objects to the vector of courses. Once the vector is populated with course objects, they can be sent to the printer. The XML manipulator is inserted into the stream prior to calling the copy() algorithm again. This is necessary because we want the format state of the stream to be set before the copy() algorithm starts its work. This design will ensure that the proper stream state is visible to the << operator. This time, the copy() algorithm uses the ostream_iterator to visit each course object in the vector. The ostream_iterator will use the operator << to insert the courses into the printer stream. The operator << assumes that the stream format state has already been set. (Therefore, set any stream formatting flags or manipulators prior to applying any algorithm.) The format the course will take in the stream is determined in the definition of the << operator. Listing 3 contains a simple implementation of an ostream_iterator:
Listing 3 A simple implementation of an ostream iterator.
template <class _Tp> class ostream_iterator { protected: ostream* _M_stream; const char* _M_string; public: ostream_iterator(ostream& __s):_M_stream(&__s),_M_string(0) {} ostream_iterator(ostream& __s,const char* __c):_M_stream(&__s), _M_string(__c) {} ostream_iterator<_Tp>& operator=(const _Tp& __value){ *_M_stream << __value; if (_M_string) *_M_stream << _M_string; return *this; } ostream_iterator<_Tp>& operator*() { return *this; } ostream_iterator<_Tp>& operator++() { return *this; } ostream_iterator<_Tp>& operator++(int) { return *this; } }; Data members are protected here (class from STL)
Notice in Listing 3 that the ostream_iterator will invoke the << operator on the object that it's inserting into the stream. This is one of the primary connections between the iostreams and the STL containers and algorithms. The ostream_iterator or istream_iterator is connected to a stream. The algorithms can use the ostream and istream iterators to access objects in STL containers. The objects in the containers have the operator << or >> defined. Figure 3 illustrates how the stream iterators form the bridge between the algorithms, containers, and the iostreams.
Figure 3 Stream iterators are used to connect algorithms, containers, and iostreams.
In Figure 3, the algorithms connect to the containers through stream iterators. The stream iterators in turn invoke stream inserters or extractors, and the inserters and extractors do the stream processing. In this case, the copy() algorithm copies our course objects to a printer properly formatted as XML.
The ability to connect algorithms and containers to streams opens a world of interesting possibilities because we can connect streams to all sorts of devices: cable modems, network cards, printers, PDAs, scanners, sound cards, and so on. For instance, the curriculum-planning software that we use at CTEST labs has a text-to-speech component. We routinely use the algorithms and containers connected to the iostreams for text-to-speech processing using a sound card. Connection to the sound card is accomplished by deriving new classes from streambuf and ostream. In our case, the streambuf class is perhaps the most important because this is where the connection is made to the sound card. In general, << and >> ultimately send and receive their bytes to a streambuf object or one of its descendants. The streambuf object performs the actual I/O. When a new I/O device needs to be addressed, the streambuf class and its derivative are usually the center of attention. Listing 4 shows a scaled-down version of our tts_buf class.
Listing 4 Scaled down version of the tts_buf class.
class tts_buf : public streambuf{ synthesized_voice * Voice; std::streamsize xsputn(const char *s, std::streamsize num); public: tts_buf(void); ~tts_buf(void); }; std::streamsize tts_buf::xsputn(const char *S, std::streamsize Num) { string Expression(S,Num); Voice->say(Expression); return(Expression.size()); } tts_buf::tts_buf(void) { Voice = new synthesized_voice; } tts_buf::~tts_buf(void) { delete Voice; }
To direct our output to a speech engine that will ultimately send it to the sound card, we override the xsputn() method of the streambuf class. Since we typically send character strings to the speech engine, we don't need to override xsputc(). xsputn() is the lowest-level method that deals with character streams, so we direct the output to the speech engine in this method. Once this is done, we can construct an ostream object with a tts_buf object. Any character strings inserted into this object will be spoken by the speech engine. Also, our user-defined inserter will format the character string appropriately (using the user-defined manipulators) before passing it to the tts_buf object. Listing 5 shows how the tts_buf object is used with an ostream object and how that object is then connected to an ostream_iterator.
Listing 5 Using the tts_buf object, ostream object, and an ostream iterator.
#include "course.h" #include <iterator> #include <algorithm> #include <vector> int main(int argc,char *argv[]) { course Course; course Course2; course Course3; vector<course> Schedule; Schedule.push_back(Course); Schedule.push_back(Course2); Schedule.push_back(Course3); tts_buf TTSBuf; ostream Speech(&TTSBuf); Speech << sspr; ostream_iterator<course> Out(Speech," "); copy(Schedule.begin(),Schedule.end(),Out); }
Notice in Listing 5 that the copy() algorithm is used to send the contents of the course schedule to the text-to-speech engine. This will cause the speech engine to pronounce the contents of each course object. The sspr (simple sound phonetic representation) is a user-defined manipulator that causes the inserter for the course object to output its start and end times3:00, 4:00, 5:00as "three o'clock," "four o'clock," and "five o'clock," respectively. A course with a start time of 12:00 would be pronounced "twelve o'clock."