C++/Output Help
Expert: Ralph McArdell - 4/20/2009
QuestionI wrote this code and it needs to output to the screen as well as a file. Well I can get it to do either or but not both at the same time and ideas and help would be greatly appreciated. Thanks
#include <iostream>
#include <fstream> //Use this program to claculate total tickets sold and total amount collected
#include <iomanip> //Use this with setprecision and showpoint
#include<string>
using namespace std;
int main()
{
ifstream inData; //Use to read from a file
ofstream outData; //Use to write to a file
double ticketPrice;
double noOfTicketSold;
cout << "Total Tickets and Total Ticket Sales" << endl;
cout << "********************************************" << endl;
inData.open("k:\inData.txt");
outData.open("k:\outData.dat");
cout << fixed << showpoint;
cout << setprecision(2);
while( inData >> ticketPrice >> noOfTicketSold)
{
cout << "Total # of Tickets Sold : " << noOfTicketSold << endl;
cout << "Total Amount Collected : " << (ticketPrice * noOfTicketSold) << endl;}
cout << "********************************************" << endl;
inData.close(); //Use to close opened files
outData.close(); //Use to close opened files
return 0;
}
AnswerOK the most obvious and easiest way to do this it to literally write all data twice - once to one stream and again to the other:
std::cout << stuffToBeWritten;
outData << stuffToBeWritten;
Obviously this can get a bit tedious, so you could always write to a string stream then write the resultant string to both output streams:
#include <sstream>
// ...
std::ostringstream oStrStrm;
oStrStrm << "write something" << dataValue << '\n';
outData << oStrStrm.str();
std::cout << oStrStrm.str();
You may need to explicitly flush one or both streams as writing std::endl to std::ostringstream will write newline and 'flush' only the string stream.
These actions could be wrapped up in a simple class, the bare bones of such a class might look as follows:
#include <sstream>
#include <ostream>
class WriteTwoStreams
{
std::ostream * pOut1_;
std::ostream * pOut2_;
std::ostringstream buffer_;
public:
WriteTwoStreams( std::ostream & o1, std::ostream & o2 )
: pOut1_(&o1)
, pOut2_(&o2)
{
}
std::ostream& out() { return buffer_; }
void flush();
~WriteTwoStreams() { flush(); }
};
void WriteTwoStreams::flush()
{
*pOut1_ << buffer_.str();
*pOut2_ << buffer_.str();
buffer_.str( "" ); // clear buffered data
}
This is a _very_ simple scheme and only shows the absolute minimum required to show the idea (e.g. there is absolutely _no_ error checking). The idea is that we create an object of type WriteTwoStreams, passing it the two streams to write to. It holds references (as pointers) to these streams. It also creates a std::ostringstream object to buffer output to until flush is called whereby the buffered characters are written first to one stream then the other then the buffer is cleared. To write data you call the out member function which returns a reference to the buffer string stream. Here is an example of its use:
#include <iostream>
#include <fstream>
// ...
int main()
{
std::ofstream outData("outData.dat");
WriteTwoStreams tee( outData, std::cout );
tee.out() << "Hello World!\n"
<< 23 << '*' << 45 << '=' << 23*45
<< std::endl;
tee.flush(); // *must* flush buffer explicitly in this simple scheme
tee.out() << "Another chunk of output!\n";
tee.flush(); // *must* flush buffer explicitly in this simple scheme
}
Again error checking has been elided for brevity.
You may be wondering why I called the WriteTwoStreams object tee. This is because there is a UNIX utility with that name that does pretty much what you ask but from the command line:
So if yourProg wrote to standard output (std::cout) then you could use tee form a UNIX command shell like so:
yourProg | tee outData.dat
The | operator pipes yourProg's standard output to the standard input to tee which writes it to each specified file (just outData.dat here) and to standard output.
If you wish to do a more thorough job then you need to delve into the workings of streams, stream buffers and the like as you would almost certainly need either a specialist stream that held two (output file) stream buffers or a stream buffer that did a similar job that was used with an existing stream type, neither of which are really concise enough topics to go into here - sorry. If you are interested in such matters then I suggest you get yourself a good reference on C++ IOStreams such as the chapter on 'Input/Output Using Stream Classes' in 'The Standard C++ Library a Tutorial and Reference' by Nicolai M. Josuttis or, for even more focus on IOStreams, 'Standard C++ IOStreams and Locales' by Angelika Langer and Klaus Kreft.
However, as a good programmer you should think "has someone done this sort of thing already? And if so can I re-use their work?" before rushing off to try to do it yourself. And indeed they have - a quick query or two in Google will lead you to the Boost IOStreams library (and possibly others). Boost maintain a set of freely available libraries that enhance the standard C++ libraries. Some such libraries make it into newer ISO C++ standards such as the TR1 library extensions. The IOStream library provide a set of code tools to make extending the standard IOStream library easier - and provide a set of such items ready for use. Boost can be found at:
http://www.boost.org/
The documentation for the Boost IOStream library (or the most recent release at the time of writing) can be found at:
http://www.boost.org/doc/libs/1_38_0/libs/iostreams/doc/index.html
This is a little daunting, but poking around reveals a set of tee types and functions which can be used to do what we wish. Here is a simple example which in fact writes to two files and std::cout:
#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <iostream>
// Short alias for boost::iostream namespace
namespace io = boost::iostreams;
int main()
{
io::filtering_ostream teeOut;
teeOut.push( io::tee(io::file_sink("out1.txt")) );
teeOut.push( io::tee(io::file_sink("out2.txt")) );
teeOut.push( std::cout );
teeOut << "Hello World!\n"
<< 23 << '*' << 45 << '=' << 23*45
<< std::endl;
teeOut << "Another chunk of output!" << std::endl;
}
I am not going to explain how this works in detail as you can look up the parts in the Boost documentation to see what I am using. The basics are that we use a special output stream provided by the Boost IOStream library - the filtering_ostream. This allows us to attach a chain of filters and a stream to the stream. Data is filtered through the filters before being written to the output stream. So in this case I specify two tee 'filters' to output to different files (using Boost IOStream library file_sinks) and then finally to output the result to std::cout. Note that you should specify std::endl to ensure all buffering is flushed before output is to appear on the console.
Hope this at least gives you some food for thought.