Point to ofstream

Started by
5 comments, last by Zahlman 17 years ago
Can I make a pointer to std::ofstream so that multiple classes can use the same stream? Iv tried but get errors when I do it like this ofstream log; ofstream* plog = &log plog << "Hello Woyld"; Is it possible or must I be doing something wrong?
Advertisement
if you use a pointer, you will probably need to dereference it

(*plog) << "Hello Woyld";

Try making a reference to it instead of a pointer

ofstream& samelog = log;
samelog << "Hello Woyld";

"I can't believe I'm defending logic to a turing machine." - Kent Woolworth [Other Space]


class cLog
{
public:
cLog();
void CreateLog(const char*);
void OpenLog(ofstream&);
void WriteLine(const char*);
void WriteWord(const char*);
void AddTag(LogTag);
void SetColour(const char*);
void SetSize(const char*);
void CloseTags();
bool AutoCloseTags;

private:
ofstream newlog;
ofstream& log;
LogTag Tag;
bool _BoldTagClosed,_ItalicTagClosed,_UnderlineTagClosed,_BigTagClosed,_SmallTagClosed,_CodeTagClosed,_StrongTagClosed,_EmpTagClosed,_H1TagClosed,_H2TagClosed,_H3TagClosed,_H4TagClosed,_H5TagClosed,_H6TagClosed,_Bold,_Italic,_Underline,_Big,_Small,_Code,_Strong,_Emp,_H1,_H2,_H3,_H4,_H5,_H6;

};


Whe I have this, i get:
cLog::log' : must be initialized in constructor base/member initializer list

but when i initialise to newlog, i get:
error C2327: 'cLog::newlog' : is not a type name, static, or enumerator
error C2065: 'newlog' : undeclared identifier

class cLog{  cLog( ofstream &os)    : log(os)  {}  ofstream &log}
Use "source" instead of "b" next time [smile]

A reference must reference something. There is no such thing as a "null" reference. If it makes sense for something to be null, use a smart pointer object instead (like boost::shared_ptr).

As for your error, you must add a constructor that takes a reference ofstream as an argument, and initialises the reference in the initialiser list. Since we can, we may as well take a generic "std::ostream" reference, allowing you to replace file logging with something crazier later on without any code changes... like a remote TCP log of some sort.

Also, consider replacing those bools with an enumeration and using bitwise operators to read and write to an integer holding them all (not a major deal, but may make the class easier to deal with ).

Finally, be careful with references as class members, they impose some interesting restrictions on the class ( I don't know of a way to make such a classes copy constructor without using a dubious looking const_cast, for instance ).

[edit:]
Looking closer, it would seem to make as much if not more sense for the Log class to own the output stream, rather than to reference another. Just replace the reference with a full instantiation, and the openLog parameter from a stream reference to a string of a filename (or move such initialisation to the constructor, after all most of us don't change the log file we are using mid-game)
So, my class has one ofstream (real_log), and one reference to it (log).
All the class funcs use log to output.

How should my constructor look?

edit:
@rip_off
I decided that I will use many instances that all output to one file you see.

edit #2:
would it be better to take this all out o a class and have the other classes that need it include it?
Quote:Original post by ConorH
Can I make a pointer to std::ofstream so that multiple classes can use the same stream?


Yes, but doing it properly is more complicated than you may think it ought to be.

The fundamental problem is that stream objects cannot be copied. This prevents you from copying objects that hold them as members, or even passing them by value to a function (instead of by reference). There's good reason for this: suppose you copied a stream, and then read from one instance. The other instance would have no way of knowing about this happening, so it would have corrupt internal data (because that data reflects the read not having taken place).

Unfortunately, references won't really help you out here, because if you declare all your objects to have a reference to some stream, that stream still has to *be somewhere*. If it's also a data member, then you have a problem, because all the objects have *some* stream (not just the "master"), and while you could set them to default-constructed streams, you wouldn't have a particularly easy way to check whether a given stream is a "dummy" or not. Plus, you're wasting memory for those objects. (Oh, and forget about using a union - won't work with references, anyway; and it still wouldn't really solve any problems.)

Beyond that, there are issues of object lifetime. References can "dangle" in the same way that pointers can, and it's undefined behaviour. In particular, if we have objects A and B, and B is then initialized with a reference to a data member of A, and then somehow A is destructed before B is, then all hell breaks loose.

What we need is a way to put the stream instance "somewhere else", have the objects *only* refer to it (and never contain it by value, because that complicates our logic), and have some way of managing the object lifetime. The natural way to do that is to dynamically allocate the stream instance (that way, it won't get destructed by "falling out of scope"), but then we need a way to find out when the stream is no longer being used, so that we don't leak the memory (or the file handle). This occurs when the *last* "sharing" instance is destructed; i.e. the instant that there are zero references to the allocated stream.

The natural way to deal with *that* is to use a reference count: we build a struct somewhere with an int and the pointer to the dynamic allocation, and provide an interface whereby we can "add clients" (increment the reference count and return the pointer value; under the assumption that the client will store that pointer somewhere) and "remove" them (under the assumption that a client has just stopped using the pointer, decrement the reference count; and if it reaches 0 as a result, delete the stream). But this is quite tricky to do ourselves.

Fortunately, there is a tool in the Boost library that will take care of this for us: boost::shared_ptr.

I'll use it to make a simple "copyable_ostream" class that wraps the shared_ptr by delegating any operator<<s to the pointed-at stream (which may or may not be shared between several instances). We can then include the copyable_ostream as a data member in other classes, and use it like a normal ostream (at least as far as the operator<<, anyway). And it won't interfere with copying the containing object. :)

class copyable_ostream {  boost::shared_ptr<ostream> os;  public:  copyable_ostream(const std::string& name, int flags = 0): os(new ostream(name.c_str(), flags)) {    if (!(os->good())) {      os.reset();      throw ios::failure(name);    }  }  template <typename T>  copyable_ostream& operator<<(const T& t) const {    (*os) << t;    return *this;  }};

This topic is closed to new replies.

Advertisement