What's the difference? (C++ question)

Started by
9 comments, last by dragonhawk360 15 years ago
Okay, I have a question. Currently, I have just finished a "FileIOStream" class, that uses the fputc and fgetc functions (and their variations) from <stdio.h>. I did this because I was able to remember them at the time I wrote the class. I recently learned of the ofstream and ifstream, and am wondering what are the advantages of these functions instead of fputc/fgetc. Are there any major advantages or am I better off leaving it as is using <stdio.h>?
Advertisement
The main differences are the level of abstraction and the support for RAII.

The RAII thing is good because it means that you won't forget to fclose() a file.

The abstraction has a number of benefits. Firstly, you can write generic functions in C++ that will write a complex type to a ostream. This means that you can write it to anything that a ostream can write to, such as a string, a file, the standard IO stream or (because ostreams are extensible) sockets or other forms of IPC.

Likewise, you can make a generic function that will read a complex type from a istream, which can be used to read your user defined types from a wide array of input sources.

If you want, you could post your class and we could give you some feedback on it.
So what your saying is that fstreams are simply extended stdio file functions? So instead of having a "WriteCharacter" and "WriteString" function, I could just use a single ofstream function? And I can't post the source just yet (still fixing FileIOStream::ReadString) but as soon as I'm done debugging it, I will post it.

EDIT:
And for the RAII thing, I don't fully understand it, but in the constructor, it opens a file stream (fopen), and in the destructor, it closes it (fclose).
Quote:Original post by dragonhawk360
wondering what are the advantages of these functions instead of fputc/fgetc.<!–QUOTE–></td></tr></table></BLOCKQUOTE><!–/QUOTE–><!–ENDQUOTE–><br><br>Under the hood, &#111;n any particular OS, they all do pretty much the same thing.<br><br><!–QUOTE–><BLOCKQUOTE><span class="smallfont">Quote:</span><table border=0 cellpadding=4 cellspacing=0 width="95%"><tr><td class=quote><!–/QUOTE–><!–STARTQUOTE–><i>Original post by dragonhawk360</i><br>Currently, I have just finished a "FileIOStream" class, that uses the fputc and fgetc functions (and their variations) from &lt;stdio.h&gt;.<!–QUOTE–></td></tr></table></BLOCKQUOTE><!–/QUOTE–><!–ENDQUOTE–><br><br>If you post the details of your FileIOStream class, we can compare and comment &#111;n how it compares to the C++ standard file stream library. Otherwise we are in the dark.
This is "FIOS.cpp":
#include <stdio.h>class FileIOStream{      private:           FILE *streamfile;           bool enabled;      public:           FileIOStream(const char *filename)           {                if ((streamfile = fopen(filename, "r+")) == NULL)                {                     printf("ERROR 001: FILESTREAM CREATION FAILED");                     enabled = false;                }                else                {                     enabled = true;                }           }           ~FileIOStream()           {                if (streamfile != NULL)                {                     fclose(streamfile);                }           }           char ReadChar()           {                if (enabled == true)                {                     char letter = fgetc(streamfile);                     return letter;                }           }           //char *ReadString(char *string, int length) //Debugging still           void WriteNewLine()           {                if (enabled == true)                {                     fputc('\n', streamfile);                }           }           void WriteChar(char text)           {                if (enabled == true)                {                     fputc(text, streamfile);                }           }           void WriteString(const char *text)           {                if (enabled == true)                {                     fputs(text, streamfile);                }           }           void ResetPosition()           {                rewind(streamfile);           }};


This is "FIOStest.cpp":
#include <stdio.h>#include "FIOS.cpp"int main(){     FileIOStream *fiostream = new FileIOStream("Debug.txt");     fiostream->WriteString("Hello, world.");     fiostream->WriteNewLine();     fiostream->ResetPosition();     char letter = fiostream->ReadChar();     if (letter != 'H')     {          fiostream->WriteLine("ERROR!");     }     else     {          fiostream->WriteLine("Done.");     }     delete fiostream;     return 0;}
First off, on a stylistic note there is no reason to use a pointer to a heap allocated FileIOStream in your main function. It simplifies everything if you keep heap allocation to a minimum.

As for your class itself, there is no need for the "enabled" bool, because you already know if it is enabled by testing the "streamfile" pointer for NULLness.

And to keep code duplication to a minimum, WriteNewLine() could be implemented like this:
void WriteNewLine(){    WriteChar('\n');}


Your ResetStream function don't check if the stream is "enabled". That is, you call rewind on a potentially null pointer.

Other than that, I would advise dropping the class and just using std::fstream objects directly. They are very easy to use if you have experience of using the C++ standard input and output objects, std::cin and std::cout.
The FileIOStream::ResetPosition() not checking for enabled was my fault. I have fixed that since. I am also going to function overload a FileIOStream::Write() so it combines all of my writing functions for function overloading practice. I am also going to do the same for my reading functions. Now, for when I begin truelly building a FIOS for my game, I'll use fstream, but for now, do you think that the above changes and more are good for practice for topics such as function overloading, operator overloading (<< and >>, mostly), and general Input and Output? Thank you for all of the constructive replies so far, btw.
The other thing that C++ streams get you is polymorphism: different kinds of streams offer the same interface. You don't have to have a separate set of functions like printf (to console) vs. sprintf (to in-memory char buffer) vs. fprintf (to file). Instead, the distinction is made according to what kind of stream is used.

ofstream file("foo.txt");stringstream text;cout << 42; // prints to screenfile << 42; // writes to filetext << 42; // writes to a hidden string in memory that's part of the stringstreamstd::string output = text.str(); // makes a copy of that string - naturally,// file and console streams don't provide *that* functionality :)
Well, it will probably work.

But it still ties your functions heavily to writing to files, rather than the more elegant approach of writing to any stream.

Lets look at how one might go about that:
class StreamSink{public:    virtual ~StreamSink() {}    virtual bool write(const char *bytes, int count) = 0;    virtual bool flush() = 0;    virtual bool eof() const = 0;    virtual bool failure() const = 0;    bool good() const { return !(eof() || failure()); }private:    // disable copying    StreamSink(const StreamSink &);    StreamSink &operator=(const StreamSink &);};class OutputStream{public:     // n.b. assumes that "sink" is allocated by new.     OutputStream(StreamSink *sink) : sink(sink) {}     virtual ~OutputStream()     {         delete sink;     }     OutputStream &write(const char *bytes, int count)     {          if(sink->good())          {              sink->write(bytes, count);          }          return *this;     }     OutputStream &flush() { sink->flush(); }     operator bool() const { return sink->good(); }private:     StreamSink *sink;};OutputStream &operator<<(OutputStream &out, char c){    return out.write(&c, 1);}// overloads for int, float, etc etcclass FileSink : public OutputSink{    FileSink(const char *filename)    :         file(fopen(filename)),    {    }    virtual ~FileSink()    {         if(file)         {             fclose(file);         }    }    virtual bool write(const char *bytes, int count)    {        if(file)        {            return fwrite(bytes, 1, count, file) == count;        }        return false;    }    virtual bool flush()    {        if(file)        {             return fflush(file) == 0;        }        return false;    }    virtual bool eof()    {        if(file)        {             return feof(file);        }        return true;    }    virtual bool failure()    {        if(file)        {             return ferror(file);        }        return true;    }private:    FILE *file;};class OutputFileStream : public OutputStream{    OutputFileStream(const char *filename)    :        OutputStream(new FileSink(filename))    {    }};

So, as a top level overview. Firstly, you will note that we have separated the task of formatting the data from writing it. The OutputStream objects first turn the data into a sequence of bytes, then they tell their stream objects to write the data. The actual formatting here is done inside the various operator<<() overloads (of which I have omitted all save one).

The OutputSink objects have a way of returning error codes, of which there are two here, EOF and general failure. Error handling is one of the big things you class lacks at the moment.

This is a supreme example of how not to do error handling:
printf("ERROR 001: FILESTREAM CREATION FAILED");


Then we see the FileSink class, which implements writing to a file using the C file manipulation functions. But I hope you can see how it would be easy enough to make a Sink backed by a TCP socket, an IPC pipe or even an in-memory buffer.

Speaking of buffers, I haven't added any additional buffering here, but the C++ stream objects do.

Finally, the OutputFileStream simply opens a FileSink, and leaves all the other classes do the hard work. An envious position to be sure. It could implement additional functionality, for example it could have a close() function.

But note how the overload functions are unaware that there is are stream operations that work on files. A single overload will send to any OutputStream object, which in turn will send it to any kind of Sink that is implemented.

This is a high level overview of how the C++ streams work. They are very flexible, far more than many beginners understand.

Do yourself a favour and avoid writing a wrapper for handling raw file operations, it has been done, all you need to do is include <fstream> (or <sstream>, or <iostream>, etc).

I haven't tested (or even compiled [grin]) the code I wrote there, it is just for illustration purposes. The C++ streams have a lot more in them.

In addition, that is just for output streams. Input streams have a few additional issues (for example, dealing with formatting errors).
Quote:
This is "FIOStest.cpp":
#include <stdio.h>#include "FIOS.cpp"// ...


Also, (and I can't believe I have missed this), never include C++ source files (.cpp)!

Read this.

This topic is closed to new replies.

Advertisement