You are here:

C++/fstream as a class member

Advertisement


Question
Would you give an example code how to write a copy constructor or assignement operator for a class that has a fstream as a class memeber? The defaults does not seem to work at compilation time. How to copy a fstream member in a overlaoding defintion?  How to pass the external file to all of the instances?

Answer
The problem is that std::fstream objects are _not_ designed to be copied.

In fact copying and assignment of iostreams is forbidden by std::basic_ios<> which, eventually, is a base of std::fstream. This is because std::basic_ios declares and does not define private copy constructor and assignment operator (operator=). This is idiomatic C++ for not copyable (or assignable). If you are unfamiliar with this idiom it works at two levels:

   1/ at compile time: nothing outside of members of the class itself can use the copy constructor or assignment operator because they are private;

   2/ at link time: because even if they could be used somehow and the code compiles, any program (or dynamic/shared object library) built using the resultant code will fail to link because the copy constructor or assignment operator is not defined and so references to them will be unresolved.

So the next question is why do you really wish to do this? What would copying an object of such a class actually mean? Should the new copy access the same file as the original? Should they access different files? If they access the same file, should they both read from the same position so each reads the same data? If so should the new copy start at the beginning and read up to where the copied object had got to? Or should they read subsequent parts of the file? For example given a simple file of numbers, one per line, read one at a time by objects of the class thus:

   File:
   -----
       1
       2
       3
       4

How should two objects of the class interact with the same file when both are reading from it?

  Object1.Read();  // Only one object, 1st read: reads 1

  // Does copying Object1 read 1 for Object2 and process as
  // if Read() had occurred for object 1?
  // Or should the stream just be positioned at the same point for
  // Object2 as for Object1?
   // Or should the stream be left at the start of the file for
   // Object 2?
   // Or should we do something else entirely?
   Object2(Object1);

   Object1.Read();  // Reads 2
   Object2.Read();  // Should this read 1, 2 or 3 or something else?
   Object1.Read();  // should this read 3 or 4?

OK so what about writing to the file? Well this seems easier to define, so long as neither object moves the file put position and therefore always appends to the end of the file. Each write operation from either object would write to the end of the file. The only question then would be: does the copy write to the same file as the original?

In light of these problems it is usually easier to keep a reference or pointer to a stream object in your class objects and maintain the actual stream object outside of these objects. Unless you actually require file stream specific functionality from within the operations of the class you can just keep a reference/pointer to a plain std::ostream, std::istream or std::iostream. This has the neat effect that your class can be used with file streams, string streams or any other type of stream, and can be made to work just as easily with the standard global stream objects std::cout, std::cin, std::cerr, std::clog.

Here is an example of a logging class that maintains a reference to its associated stream:

   class Logger
   {
       std::ostream &   iStream;

   // ...

   public:
       explicit Logger( std::ostream & strm=std::clog ) : iStream(strm) {}

   // ...

   };

This code is part of some experimental code I was working on a short time ago. I have left out much of the details I was in fact interested in as it is not relevant here. This will permit copying but not assignment as the value of the iStream member, as a reference, cannot be modified after it has been initially set (which you will note is done in the member/base initialisation section of the explicit constructor), viz:

 Logger log1;
 Logger log2(log1);  // copying OK:
         // reference initialised in copy constructor

 Logger log3;
 log3 = log1;  // ## Oops! Assignment BAD, cannot re-assign reference

To get around this restriction you can use a pointer to a stream:

   class Logger
   {
       std::ostream *   iStream;

   // ...

   public:
       explicit Logger( std::ostream & strm=std::clog ) : iStream(&strm) {}

   // ...

   };

Note the address-of operator (&) before initialising the (pointer) iStream member. Of course this makes using operators slightly more hassle as you have to remember to de-reference the stream pointer before using it, as in the example WriteSomething member function below:

   class Logger
   {
       std::ostream *   iStream;

   // ...

   public:
       explicit Logger( std::ostream & strm=std::clog ) : iStream(&strm) {}

   // ...

       void WriteSomething()
       {
         *iStream << "Something!\n";
       }
   };

In these examples I have used the default stream argument value to the constructors of log1 and log3 so they are built referring to the std::clog stream, which is usually mapped to the console or terminal associated with the process.

The effect of using a reference or pointer to a stream is that all copies share that reference or pointer and so all refer to the same stream. It also implies that the life time of the streams referenced or pointed to _must_ be at least as long as that of the last Logger object that uses them.

Again, if you require file stream specific operations within your class then replace std::ostream with std::fstream in my examples.

A more complete example might:

- Have constructors that cause a new stream to be created (using new fstream(filename,mode) for example)

- Store the returned pointer from new into a reference counted smart pointer such as std::tr1::shared_pointer, or its pre-cursor boost::shared_pointer (see the Boost libraries at http://www.boost.org/, http://www.boost.org/libs/libraries.htm, http://www.boost.org/doc/html/boost_tr1.html, http://www.boost.org/libs/smart_ptr/smart_ptr.htm), as implementations of the TR1 library update to the C++ standard library are not at all common yet! The cool thing about such a smart pointer is that only the last reference causes the actual wrapped and pointed to object to be deleted, which handles the life time and cleanup issues nicely for us.

A revised example (using std::ofstream as I am only showing output) looks as follows:

   class Logger
   {
     typedef boost::shared_ptr<std::ofstream>   OFStreamPtrType;

     OFStreamPtrType iStream;

   // ...

   public:
     explicit Logger( char const * pathName )
         : iStream( new std::ofstream(pathName) )
         {}

   // ...

       void WriteSomething()
       {
         *iStream << "Something!\n";
       }
   };

The differences are just those described above. The constructor now takes a pathname for a log file to use, creates a new stream for use with the new object and stores the pointer to it in a boost::shared_ptr (install the Boost libraries and #include <boost/smart_ptr.hpp> or <boost/shared_ptr.hpp>), which is the revised type of the iStream member (I used a typedef type alias as this is common practice for long/complex types).

We can use the revised logger like so:

 Logger log1("testlog.txt");
 Logger log2(log1);
 Logger log3("badlog.txt");
 log3 = log1;

 log1.WriteSomething();
 log2.WriteSomething();
 log3.WriteSomething();

In this simple example there is no default constructor so log3 is constructed using a different file name. When run we see that because we never wrote anything to log3 before it has log1 assigned to it the badlog.txt file is empty and testlog.txt contains:

   Something!
   Something!
   Something!

Now the semantics of this version of the Logger class means that the example usage creates 2 new stream objects that will require deleting: One when log1 is created and the other when log3 is created. This second stream becomes redundant as soon as log1 is assigned to log3. The other stream should be deleted when log1, log2 and log3 are all deleted.

Luckily the semantics of the shared_pointer used for iStream mean that as soon as the pointer it holds changes the reference count on the exiting object pointed to is decremented and if nothing else is referring to it (i.e. the reference count goes to zero) then the pointed to object is deleted. This is the case here - the original stream object is deleted as part of the log3 = log1 assignment as log3's original stream will have nothing else referring to it after this statement completes.

Likewise when log1, log2 and log3 are destroyed at the end of main their iStream shared_pointers are deleted causing the pointed to (remaining) stream object's reference count to again be decremented: from 3 to 2; from 2 to 1; and finally from 1 to 0 whereupon the stream object is deleted.

Note the each time a new reference is added to the same held pointer the reference count is incremented. So initially the log1 stream shared pointer has a count of 1, then when log2 copies it, the count goes up to 2. Finally when log3 assigned log1 the count becomes 3.

I know the above sounds long winded and complex, but suffice to say that using reference counted smart pointer types such as boost::shared_pointer can make our lives as C++ developers one heck of a lot simpler!

Hope this has been of some use.  

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.