void doSomething() { // Create a temporary file ofstream tempf(TEMPFILENAME); if (!tempf) throw FileException(TEMPFILENAME); try { // Do things with the temporary file // ... // Show a pretty picture displayJpeg("aPrettyPicture.jpg"); // Do more things with the temporary file // ... } catch (...) { tempf.close(); remove(TEMPFILENAME); throw; } // Delete temporary file tempf.close(); remove(TEMPFILENAME); }
We catch any exception that might be thrown by the code protected
by the try block, clean up, and rethrow that same exception. Rethrowing
the exception is a good idea, because otherwise the caller of
doSomething
would see a normal return and think everything was
fine.
One way to solve this problem is:
class TempFile { public: TempFile(const char* fn) : filename(fn), ofs(fn) { if (!ofs) throw FileException(fn); } ~TempFile() { ofs.close(); remove(filename); } ofstream& stream() // The name "stream" is our choice; it could be anything { return ofs; } private: const char* filename; ofstream ofs; }; ... void doSomething() { // Create a temporary file TempFile tempf(TEMPFILENAME); // Do things with the temporary file tempf.stream() << "Hello!" << endl; tempf.stream() << 123; tempf.stream().put('='); tempf.stream().write("123", 3); tempf.stream().put('\n'); tempf.stream().flush(); // Show a pretty picture displayJpeg("aPrettyPicture.jpg"); // Do more things with the temporary file tempf.stream() << "Goodbye!" << endl; }
But just so I don't leave you with the wrong idea, TempFile can be improved. Here are some problems:
void f() { char s[1000]; strcpy(s, "abcde"); TempFile tf(s); strcpy(s, "fghij"); } // destructor tries to remove a file named fghij!
The problem here is that the TempFile contructor stored a copy of the pointer passed to it, not a copy of the characters pointed to. The simplest fix is use a C++ string to make sure the characters are copied into storage that will be properly cleaned up:
class TempFile { public: TempFile(const char* fn) : filename(fn), ofs(fn) {...} ~TempFile() { ... remove(filename.c_str()); // remove requires a C string, not a // C++ string } private: string filename; ... };
If we wish, we could have TempFile's contructor take a C++ string instread of
a const char*
.
Another problem is this:
void g() { TempFile tf("abcde"); TempFile tf2(tf); // What should this mean? tf2 = tf; // Or this? } // destructor for tf2 removes the file abcde, so what does the destructor // for tf do?
The easiest solution is to disallow copying and assignment of TempFiles:
class TempFile {... private: ... TempFile(const TempFile& other); TempFile& operator=(const TempFile& rhs); };
The last problem is that using a TempFile is a bit awkward:
tempf.stream() << "Hello!" << endl; tempf.stream() << 123; tempf.stream().put('=');
We could enable an automatic conversion from a TempFile to an ofstream
reference by changing the stream
member function to
operator ofstream&() { return ofs; }
but there are good arguments for not enabling automatic conversions
willy-nilly, so let's leave stream
as it is.
If we anticipate that by far the most common use of a TempFile would be with operator<<, we could make that easy for clients to use, and leave the awkwardness only for the rarely used cases. We could write a bunch of operator<< overloads, one for each kind of right hand side operand, but since all those functions would do the same thing (which is forward the call to the corresponding ofstream's operator<<), we could use a template:
template<typename T> inline TempFile& operator<<(TempFile& tempf, const T& rhs) { tempf.stream() << rhs; return tempf; }Now the code above would be
tempf << "Hello!" << endl; tempf << 123; tempf.stream().put('=');