FilePointer holds a FileWrapper, which is a wrapper/shim for a FILE* which implements some RAII policy (currently either close or flush on destruction). FilePointer is used to hide the policy, and give an opaque interface for all consumers of a file stream.
The main thing seems to be that I've defined an operator->() (necessary because of how the C run-time library is written).
class FilePointer
{
FilePointer() : m_pFileWrapper(nullptr) { }
FilePointer(FileWrapper * pfw) : m_pFileWrapper(pfw) { }
FilePointer(const FilePointer & rhs) : m_pFileWrapper(rhs.m_pFileWrapper), m_references(rhs.m_references) { }
// accessors
operator FILE* () { return *m_pFileWrapper; } // allow rewind(fp), etc. !WARNING! This is the operator that allows for much mischief!
FILE* operator -> () { return *m_pFileWrapper; } // allow the C run-time library macros to work
FILE* get() const { return *m_pFileWrapper; } // explicit access
// comparisons
bool operator == (const FilePointer & rhs) const { return m_pFileWrapper == rhs.m_pFileWrapper; }
bool operator == (FILE* fp) const { return fp == *m_pFileWrapper; }
bool operator != (const FilePointer & rhs) const { return m_pFileWrapper != rhs.m_pFileWrapper; }
bool operator != (FILE* fp) const { return fp != *m_pFileWrapper; }
//////////////////////////////////////////////////////////////////////////
// Binary object I/O
// reads a single item of type T from our stream
template <typename T>
void read(T & thing)
{
// don't allow us to generate a 'read pointer' function!
BOOST_STATIC_ASSERT(!(boost::is_pointer<T>::value));
if (!fread(get(), thing))
throw IOError(__FUNCTION__);
}
// reads in a single item of type T from our stream as a return value (e.g. char c = read<char>())
template <typename T> T read() { T thing; read(thing); return thing; }
// write object
template <typename T>
void write(T const & thing)
{
// don't allow us to generate a 'write out a pointer' function!
BOOST_STATIC_ASSERT(!(boost::is_pointer<T>::value));
// write out the binary of thing
if (!fwrite(get(), thing))
throw IOError(__FUNCTION__);
}
protected:
FileWrapper * m_pFileWrapper; // pointer to FILE* wrapper
ReferenceCount m_references; // reference count
};
class FileWrapper
{
public:
// access the raw FILE*
FILE* get() const { return this ? m_fp : nullptr; }
operator FILE* () { return this ? m_fp : nullptr; }
// relinquish ownership of our FILE*
FILE* release()
{
TRACE_FILE_WRAPPER("%s[%p] releasing %X\\n", typeid(*this).name(), this, m_fp);
FILE* fp = m_fp;
m_fp = NULL;
return fp;
}
// explicitly close the file
virtual int close()
{
// fclose() docs indicate that closing a NULL stream
// results in the invalid parameter handler being called
// so we simply indicate success if no file handle is assigned to us
if (!m_fp)
return 0;
// execute the actual close
const int error = ::fclose(m_fp);
TRACE_FILE_WRAPPER("%s[%p] closed %X (status = %d)\\n", typeid(*this).name(), this, m_fp, error);
// nullify ourself only if we succeeded in closing the file stream
if (!error)
m_fp = NULL;
return error;
}
// destructor (polymorphic to allow subclasses to terminate properly)
virtual ~FileWrapper()
{
TRACE_FILE_WRAPPER("%s[%p] deleted\\n", typeid(*this).name(), this);
}
protected:
// force only subclasses to really be used by hiding the ctor
// constructor
FileWrapper(FILE* fp) : m_fp(fp)
{
TRACE_FILE_WRAPPER("%s[%p] created %X\\n", typeid(*this).name(), this, m_fp);
}
// disallow copy ctor and assignment
FileWrapper(const FileWrapper &);
FileWrapper & operator = (const FileWrapper &);
// the underlying file
FILE* m_fp;
};
Is this enough information?
I am only realizing that the dereference "correction" only occurs because I have included an operator->(), and NOT because it is a type of FILE*, which I had forgotten (this class has evolved over the years).
So, I guess my suggestion is: Can I tell VAX to NOT assume I mean -> when I type . for this and other classes like it?