From time to time, I will not use streams, as I sometimes prefer the console output, although, as you may know already, that also is handled like a file, so every trick that works on the console IO will compile and run just as well on files. For this article, a minimal knowledge of STL containers and iterators and how they work is necessary.
I won’t promise that I’m going to present every little trick, but I will try to show and discuss a few interesting ones. The stream part already forms a pretty strong basis for programming for anyone, but as you will see, once we combine it with some algorithms (found inside the <algorithm> header) it will be even simpler to use, and offer more.
First, let me point out that, as with any stream, the manipulators will work also with the files. If you have not heard about them in general, or how you should handle/use them, please search for my articles covering it. I dedicated two separate articles to the topic, and both of them have appeared (if not, then they are queued) here on the Developer Shed Network.
Printing from Containers
Probably from the first day that you started learning the C++ language, you’ve heard that you should start using the STL library, as it offers great containers. If you initially started with the C programming classes and you like arrays, I urge you also to get used to the vector container at least.
Often, you have a collection of types in a vector and you want to print them to the console (or a file) separated by a space, or every item in new line. Writing this can be done in just a line:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
std::vector<int> numbers;
numbers.push_back(97);
numbers.push_back(98);
numbers.push_back(99);
copy(numbers.begin(), numbers.end(), ostream_iterator<char>(cout, “…n”));
return 1;
}
We use the copy algorithm; it copies from our container to the stream. The real strength of this design is the ostream_class and its parameters. The class returns an iterator that will point out where to print the content between the first two iterators of the copy. In addition, the ostream_iterator class is highly customizable, with inputs like output format, where to put it, and what delimiter will be used between the members.
The template argument must be one for which the insertion operator is declared. As long as all of this is completed, you can call this method even for your own custom class, as the operator<< will be used to copy the streams from the container to the output stream. The result in the upper case looks impressive; we just converted the int values into their ASCI chars.
a…
b…
c…
Press any key to continue . . .
Reading Inside a Container
You can use the same trick in the other direction (input), since it’s just as easy. Let us suppose you have the input waiting for input in a predefined way. For example, we will try with a sequence of int types. Now we will copy in the other direction, so we need to indicate from where to copy, and to where. The first two members are the istream_iterator that will delimit the borders. When we pass a class to it, it will return an iterator to its start, and no parameter will just point to the end of file.
The last argument is a little more interesting. Under normal conditions, whenever you make a copy, you make sure that the target is large enough to hold the incoming stream of data. This is achieved by calling the destination resize() function, but in the case of this iterator type, this process is simplified by calling the push_back() method on the destination iterator. Hence, a vector can handle any size as long as you have enough physical and virtual memory available.
#include <iostream>
#include <vector>
#include <algorithm>
#include <fstream>
using namespace std;
int main()
{
vector<int> numbers;
ifstream in;
in.open(“In.txt”);
if (!in)
{
return 0; // terminate program, nothing more to do
}
copy( istream_iterator<int>(in), istream_iterator<int>(), back_insert_iterator<vector<int>>(numbers));
copy(numbers.begin(), numbers.end(), ostream_iterator<char>(cout, “…n”));
return 1;
}
The input file contained the following:
97 98 122 100 101A 12
With the output:
a…
b…
z…
d…
e…
Press any key to continue . . .
The rule stands. If you have defined a special class, and if you define the extraction function (operator >>) for it, you will be able to call it for this kind of input. For each member, the extraction function will be called to resolve the input. Naturally, you need to call the iterator creations to those kinds of template arguments.
Writing to Two Streams
Often, you may need to stream your data into two places at the same time. A perfect situation similar to this is when you need to dump the screen’s content to an output file. Therefore, whatever will be printed to the screen will be contained by a file as well. To resolve this, you always need to create another stream, and later call an insertion process for both the console and the file.
This is counterproductive. Besides, you might forget one step and then struggle due to a strange inconsistency. The perfect solution is to create a stream type to which we can bind both of them. Deriving from the ostream class is not a good idea, as this offers no virtual over-writable functions.
Doing the same thing at the streambuffer class is possible, as I have described in my article called “Extending the Basic Streams in C++”. Feel free to search for it here on the network if you are interested in learning about this approach in more detail about this approach. However, there is a much simpler way to do it.
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
template<typename charType,
typename twinCharTraits = std::char_traits<charType> >
struct basic_TwinStream : basic_ostream<charType,twinCharTraits>
{
//define our new basic type
typedef basic_ostream<charType,twinCharTraits> TwinType;
// constructor -> we initialize the two stream references
// construct this by using as buffer for the stream the // buffer of the first
basic_TwinStream(std::ostream& os_1, std::ostream& os_2)
: TwinType(os_1.rdbuf()), m_os_1(os_1), m_os_2(os_2)
{ }
// declare the insertion operator, this will be called on // both of them
template<typename T>
basic_TwinStream& operator<<(const T& t)
{
m_os_1 << t;
m_os_2 << t;
return *this;
}
// a insertion operator for the manipulators
basic_TwinStream& operator<<
(TwinType& (__cdecl *manip)(TwinType& ))
{
m_os_1 << manip;
m_os_2 << manip;
return *this;
}
private:
std::ostream& m_os_1;
std::ostream& m_os_2;
};
typedef basic_TwinStream<char> TwinStream;
typedef basic_TwinStream<wchar_t > WTwinStream;
int main()
{
ofstream out(“OutIn.txt”);
TwinStream oneTwinStream(cout, out);
oneTwinStream <<left << setw(25) << setfill(‘@’)
<< “Yeti is from north” << endl;
}
The key observation here was to detect that, for a stream, the most-used function is the insertion operator. If we can create a class where we simply overwrite, we are close to a perfect situation. However, another detail must be considered. What if we want to use the manipulators to format our output? Implementing in this fashion is necessary, as we will lack any other functions.
Before we venture further, let us see the result. Here are the outputs first, straight from the console and followed by the output into the OutIn text file:
Yeti is from north@@@@@@@
Press any key to continue . . .
Yeti is from north@@@@@@@
As you can see, we will be losing the side of available functions to call. In fact, all that we can do is call the insertion operator on both types and manipulators. Calling more advanced functions, like the unsetf() or setf(), is on the list of things to be implemented, and I will leave this task to the reader. The fact that the other functions are missing is a good thing, as the user cannot use some of them that make no sense in this context.