Spring 2006 CS 188

Homework 2 Solution

  1. 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.

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