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('=');