The Developer’s Cry

Yet another blog by a hobbyist programmer

File wrapper class in C++

Last week my good friend Brian (see picture on the right) and I encountered an interesting problem: wrapping up a file descriptor in a File class. Making class wrappers in C++ generally isn’t all that hard, but this particular one was a lot more tricky than we had expected.

What we wanted to have was a File class that would act the following way:

So that you could write code like this:

File f = open("filename");
f.write("hello\n");

// destructor closes the file

You might code open() like this:

File open(const char *filename) {
    File f;
    f.open(filename);
    return f;
}

Unfortunately, this code doesn’t work, as it has a bug. Do you see it already?
The thing is, when you return f, the variable f gets copied [by means of the copy constructor] and then the destructor gets called in the original f, because it goes out of scope. Since our destructor closes the open file, the result is that the file is closed once open() returns. Read that last sentence again, argh.

There are two issues at play here:

  1. The copy constructor doesn’t work because you can’t properly copy an open FILE* object.
  2. Exiting the function causes the destructor to run, closing the file—despite the fact that we are returning the instance.

Can I have the attention of the class

How to solve this problem, and no ugly hacks, please. A good solution lies in using shared_ptr. You can copy a shared_ptr and safely return it, and its internal reference count will cause the File instance to stick around. The File object will not destructed until it really needs to be.
[More specifically, the shared_ptr keeps a pointer to dynamically allocated memory, which never auto-destructs like instances on the stack do. The copy constructor and operator=() of shared_ptr happily copy the pointer, but while doing so it increments a reference counter that keeps track of how many shared_ptr instances are currently referencing the dynamically allocated object. The destructor of the shared_ptr decrements the reference count, but does not delete the pointer until the refcount drops to zero. So, a shared_ptr can be used to ‘keep objects around’ for as long as they’re needed].

Beware, you can’t simply dump a FILE* into a shared_ptr, since the FILE* may be allocated with malloc() rather than with new. And besides, a FILE* opened (or allocated) with fopen() must be deallocated using fclose(). Therefore we wrap the FILE* into another new class named FileStream and use that class with the shared_ptr.

#include <cstdio>
#include <tr1/memory>

class FileStream {
public:
    FileStream(FILE *f) : f_(f) { }

    ~FileStream() {
        if (f_ != NULL)
            std::fclose(f_);
    }

    ...

private:
    FILE *f_;
};

class File {
public:

    // copy constructor copies the shared_ptr
    File(const File& f) : stream_(f.stream_) { }

    ...

private:
    std::tr1::shared_ptr<Filestream> stream_;
};

File open(const char *);

Wrapping up

This is one of the reasons why C++ is so hard. In any other language, you’d just return the basic File object and be done with it. Even simple things like returning a value is hard in C++. You need a degree in Computer Science to even attempt it.
[It makes sense when you realize that upon exiting the subroutine, the stack is cleaned and therefore the destructor is called. But we’re returning a value that is on that stack … so the value needs to be copied to another place, first].

Of course, I should’ve used the std::fstream class to begin with … but std::fstream doesn’t give me fdopen() nor popen(). It feels so unfinished. The documentation doesn’t say whether its destructor closes the stream. I didn’t feel like fixing fstream, so I went with FILE*.

C++ is a nice language but sometimes trivial things can get quite complex. Situations like this remind you that coding in C++ is really hard.