replicate ostream (operator<<) ?

Hi,
I apologise if I have placed this in the wrong section of the forum, it is my first post.
I wrote the following simple class to stream the output either to console or to a log file (something like a ostream wrapper):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Log {
   public:
      // Constructors
      Log ( std::ostream& );
      Log ( std::ofstream&, std::string );

      // Destructor
      ~Log ();

      std::ostream& operator<<(const std::string& str)
      {
         return m_out << str;
      }

   private:

      // data member
      std::ostream& m_out;
};

Then, in the main (or in another class) I do something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main() {
  ...
  Log* pLog;
  std::ofstream mfile;

  if ( selectedLogFile ) // stream to logfile
  {
     mfile.open("somefile");
     pLog = new Log( mfile, "somefile" );
  }

  else // stream to console
     pLog = new Log( std::cout );

  ...
  (*pLog) << "text1" << " text2" << "text3\n";
  ...
   if ( file ) mfile.close();

return 0;
}

Up to this point everything works fine. However, in some cases I would like to stream part of my output both to logfile and console. In principle I can do this by duplicating lines in the main, e.g.:
1
2
  (*pLog) << "text1" << " text2" << "text3\n";
  std::cout << "text1" << " text2" << "text3\n";

but this makes the code quite ugly. Therefore, in my Log class I tried to replicate the stream and print it to console upon request:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Log {
   public:
      // Constructors
      Log ( std::ostream& ) : m_isFile(false) {};
      Log ( std::ofstream&, std::string ) : m_isFile(true) {};;

      // Destructor
      ~Log ();

      std::ostream& operator<<(const std::string& str)
      {
         m_buffer += str;
         return m_out << str;
      }

      // public method
      void copyToConsole() 
	  {
	     if (m_isFile) std::cout << m_buffer.c_str() << std::flush;
	  };

   private:

      // data members
      std::ostream&  m_out;
	  std::string    m_buffer;
	  bool           m_isFile;
};

Then, in the main I did:
1
2
3
  pLog = new Log( mfile, "somefile" );
  (*pLog) << "text1," << " text2," << " text3\n";
  (*pLog).copyToConsole();

Unfortunately, the result is:
1
2
  logfile: text1, text2, text3
  console: text1,

Is there a way to print text2 and text3 also to console without splitting the initial line into three lines and call copyToConsole() after each one ?
You might consider looking at the boost::iostreams library. It has the ability to tee output
to two different devices, though I don't think it can selectively do it.

An alternative is to write your own stream manipulator that turns on/off "tee-ing" when
you want. Then you would do something like

 
log << "This goes only one place" << tee << "This goes both places" << untee << "This goes one place" << std::endl;


I'd definitely lose the "pointer-to-stream" though because the syntax (*pLog) << ... is already ugly.
Hi jsmith,

thanks for your tips. I read a bit about "tee"-ing streams and found an interesting example here:
http://wordaligned.org/articles/cpp-streambufs

However, meanwhile I managed to understand my problem (with a little bit of help from a friend). It seems that when I am doing:

(*pLog) << "text1," << " text2," << " text3\n";

the first "<<" returns a reference to my m_out object, while the second "<<" returns a reference to ostream. Which means that my overloaded operator<< is called only once, and for this reason I see on console only "text1,".

So, I changed the overloading to:
1
2
3
4
5
6
Log& operator<<(const std::string& str)
{
   m_out << str;
   if ( m_copyToFile ) std::cout << str;
   return *this;
}

to make sure that I get always a reference to my Log object. The data member m_copyToFile is boolean and is set via a public method. This allows me to copy parts of the stream to console. Here is an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {

   std::ostream logfile ( "somefile" );
   Log myLog ( logfile, "somefile" );

   myLog << "text1, " << "text2\n";

   if ( myLog.isFile() ) myLog.copyToConsole( true );  // enable
   myLog << "text3, " << "text4\n";

   if ( myLog.isFile() ) myLog.copyToConsole( false ); // disable
   myLog << "text5, " << "text6\n";

 return 0;
}

which produces the following result:
1
2
3
4
5
logfile: text1, text2  
         text3, text4  
         text5, text6  

console: text3, text4  


Cheers,
buburuz
Yes - you're right. I didn't look closely at your operator<< declaration. That would do it.

As a suggestion, you might consider providing a full operator<< interface, including manipulator support so that you can use your Log object the same as std::cout and such.
Topic archived. No new replies allowed.