Ruminations on the standard C++ library — Files—Follow the stream
The christmas break was almost upon us – only a few more things to do before I
could leave the computer lab, pack up my stuff and head home for a few weeks
of… exam reading. The joy of having exams in January can never be
underestimated.
Clicking away on the keyboard, I was working on a program that would store a
series of strings in a file, and read them in again later on. What I was soon
to learn was an atrocity looked rather like this:
#include <cstdio> #include <cstdlib> void store() { FILE* fp = fopen("store.txt", "w+"); char buf[1024]; strcpy(buf, "This"); fprintf(fp, "%s\n", buf); strcpy(buf, "is"); fprintf(fp, "%s\n", buf); strcpy(buf, "a"); fprintf(fp, "%s\n", buf); strcpy(buf, "test."); fprintf(fp, "%s\n", buf); fclose(fp); } void read() { FILE* fp = fopen("store.txt", "w+"); char buf[1024]; while (!feof(fp)) { fgets(buf, 1023, fp); printf("%s", buf); } fclose(fp); } int main() { store(); read(); return 0; }
I nodded contentedly. It stored the strings in the file, it read them back in
and displayed them one after another on the screen. Great… or so I thought.
Unfortunately Ialmenes should prove that once again, I knew nothing of C++.
I was flabbergasted seconds before I heard the book slam shut.
‘Hmm… I thought we had gone over this “writing C in C++” thing before, didn’t we?’
Ialmenes’ voice said out softly behind me.
‘I… uh… suppose so. What’s wrong with this now? I even remembered to use the correct C++ headers [1]!’ I said.
‘That you did,’ Ialmenes nodded, ‘but you’re using C string literal handling, and C file handling. Why not move into the C++ world now that you’re programming using it and let the standard library take care of your worries?’
I knew the better of insisting to use this so I shrugged, ‘Ok, then show me how.’
Smiling he grabbed the keyboard from me. ‘Ok, first off, let’s use C++ strings instead. The C++ class string is placed in the header <string> and you use it by instantiating an object of type std::string.’
#include <cstdio> #include <cstdlib> #include <string> void store() { FILE* fp = fopen("store.txt", "w+"); std::string buf; buf = "This"; fprintf(fp, "%s\n", buf.c_str()); buf = "is"; fprintf(fp, "%s\n", buf.c_str()); buf = "a"; fprintf(fp, "%s\n", buf.c_str()); buf = "test."; fprintf(fp, "%s\n", buf.c_str()); fclose(fp); } void read() { FILE* fp = fopen("store.txt", "w+"); char buf[1024]; while (!feof(fp)) { fgets(buf, 1023, fp); printf("%s", buf); } fclose(fp); } int main() { store(); read(); return 0; }
‘As you can see we have managed to get rid of the hardcoded size of the buffer in the store function. This is, in case you’re wondering, a good thing. The fewer hardcoded things you have the better. Now, as fgets, and all the other C library functions, belong with C they don’t have support for reading into C++ strings. So, in order to do that, let’s move over and use the iostream library that is part of the standard library in C++. We need to include two more headers for this: <iostream> and <fstream>. The first contains objects to write to the standard output, read from standard input and the like – and the second contain classes for reading and writing from/to files.’ Ialmenes said, without an apparent end to this lecture.
‘Let’s do away with the C library stuff. Without further ado let’s change things.’
Ialmenes typed like a madman.
#include <iostream> #include <fstream> #include <string> void store() { std::ofstream fp("store.txt"); std::string buf; buf = "This"; fp << buf << '\n'; buf = "is"; fp << buf << '\n'; buf = "a"; fp << buf << '\n'; buf = "test."; fp << buf << '\n'; fclose(fp); } void read() { std::ifstream fp("store.txt"); std::string buf; while (std::getline(fp, buf)) { std::cout << buf; } } int main() { store(); read(); return 0; }
‘This might require a bit of explanation.’ Ialmenes said, ‘So let’s go over it carefully from the beginning.’
‘The class, std::ofstream is the out-file stream class, or said in another way, it is the class that supports outputting data to a file. We define an instantiation of this in the object fp on which we call the constructor with the const char* string literal “store.txt”. Wrapping it all up it means that we are opening an out-file stream to the file store.txt that will be created if it doesn’t exist or overwritten automatically if it does.’
Ialmenes looked at me to get a nod that I was generally following his stream of
words.
I nodded a bit weary,
‘Yeah, we’re writing the following data to store.txt just like we do when writing to std::cout, right?’
Ialmenes nodded and continued.
‘Yes, a string literal is assigned to a std::string buffer and this is subsequently written to the file and appended with a newline. Now, if we didn’t need to read in things from the standard input, which isn’t in our example right now, then we could merely have done something like this.’
Ialmenes typed up a small sample function.
void store_sample() { std::ofstream fp("store.txt"); fp << "This" << '\n' << "is" << '\n' << "a" << '\n' << "test." << '\n'; }
‘With this everything would’ve been written without having to buffer the contents somewhere. If you are sufficiently fluent in C++ you can actually tie the standard input directly to a file, but let’s not go there right now.’
Ialmenes smiled and continued,
‘Now, once the store() function finishes then the destructor for fp is automatically called, and this closes the function and writes any waiting output. This way you don’t need to worry about forgetting to call fclose on the file handle. Another benefit, if you ask me.’
Amazing what this guy could pull out of his sleeve on C++ at that speed. While
trying to place everything properly I just nodded and let him continue his
small lecture, which he seemed to enjoy rather much.
‘So let’s look at read(). Where std::ofstream is the out-file stream then std::ifstream is the in-file stream. And where the former worked much like std::cout then the in-file stream works much like std::cin. Now, apart from this similarity we aren’t going to use it like we normally use std::cin, because we want to read entire lines of strings. This means we want to read until we meet a newline character. This is achieved by the general algorithm, std::getline, which works on any in-stream (that is in-file stream, std::cin, and a few other, more obscure ones that exist). It does exactly what it says, reads in a line and places it in the string that we specify, namely buf.’
Ialmenes cleared his throat,
‘and just like with the std::ofstream then the destructor of fp for the in-file stream here will be called when we go out of scope, that is leave read(), and once again we won’t have to bother with calling fclose for any file handle.’
I nodded – it seemed pretty straightforward, actually, ofstream for writing,
ifstream for reading. ofstream created the file, ifstream read from a file. I
looked up with a glimpse of understanding in the eye.
‘So what if the in-file doesn’t exist, then what? won’t things fail?’
‘Well, you didn’t check for that either in your C program, but yes, let’s add some error checking for good measure. It’s pretty much nothing else than making sure that the stream (file) is open by calling is_open like this.’
Ialmenes said and added a few more lines.
#include <iostream> #include <fstream> #include <string> void store() { std::ofstream fp("store.txt"); if (!fp.is_open()) { std::cerr << "Unable to open store.txt for output." << std::endl; exit(EXIT_FAILURE); } std::string buf; buf = "This"; fp << buf << '\n'; buf = "is"; fp << buf << '\n'; buf = "a"; fp << buf << '\n'; buf = "test."; fp << buf << '\n'; fclose(fp); } void read() { std::ifstream fp("store.txt"); if (!fp.is_open()) { std::cerr << "Unable to open store.txt for input." << std::endl; exit(EXIT_FAILURE); } std::string buf; while (std::getline(fp, buf)) { std::cout << buf; } } int main() { store(); read(); return 0; }
‘And voilà that should make it work, using C++ too and all.’
Ialmenes smiled and pushed the keyboard away from him.
‘Enjoy the vacation, and remember to read some intelligent books [2].’
Ialmenes turned away and walked away.
I managed to shout a ‘Thanks!’ after him, but either he did not hear, or he did not show any sign that he did.
References
-
There are no headers with .h in the C++ Standard Library.
Headers such as iostream.h aren’t mentioned in the Standard (ISO/IEC 14882) at
all, and as such are compiler-specific. Furthermore, if they do come with your
compiler then they aren’t guaranteed to work the way that the standard
specifies the iostreams should work, chances are near nil that it will.
Likewise the C Standard Library that exists within C++ uses headers without
postfixed .h, i.e. stdio.h is cstdio, math.h is cmath and so forth. -
Some good choices would be:
- William Shakespeare: Romeo & Juliet or Henry the 5th
- Carrol Lewis: Through the Looking Glass
- John Keats: Endymion
And for the more programming inclined:
- Andrew Koenig & Barbara Moo: Accelerated C++, Addison-Wesley Longman, Inc.
- Andrei Alexandrescu: Modern C++ Design, Addison-Wesley Longman, Inc.
Unless you are well versed in C++ I would councel against reading the latter of
these, though. May you all have a nice holiday (yes I’m aware it’s probably
well passed by now).
Sub-Pages
Categories
Archives
- November 2009
- October 2009
- June 2009
- May 2009
- February 2009
- January 2009
- December 2008
- November 2008
- October 2008
- September 2008
- August 2008
- May 2008
- April 2008
- March 2008
- February 2008
- January 2008
- December 2007
- November 2007
- October 2007
- September 2007
- August 2007
- June 2007
- May 2007
- April 2007
- March 2007
- February 2007
- December 2006
- November 2006
- October 2006
- September 2006
- June 2006
- April 2006
- March 2006
- February 2006
- January 2006
- December 2005
- November 2005
- October 2005
- September 2005
- May 2005
- April 2005
- March 2005
- February 2005
- January 2005
- December 2004
- June 2004
- April 2004
- February 2004
- November 2003
- January 2003
- November 2002