[comp.lang.c++] Deriving new stream types: tstream

jak@cs.brown.edu (Jak Kirman) (01/31/91)

I would like to create a type which behaves precisely as does an ostream
except that the constructor would take two ostream references, and every
operation would affect both ostreams equally.

This would be very useful for debugging, where I often want a complete
transcript of the debugging messages in a file, and also some of the
debugging messages on the terminal (a run-time switch can make different
outputs go to the file or to the tstream).

If I spent a long time poring over the manuals, I could probably figure
out how to do this right, but I thought I would first ask if anyone else
has done this, or anything similar enough to provide either a cookbook
approach or some hints on what to avoid or worry about.

I managed to get something similar working by deriving a type
tfilebuf from filebuf, and a type tstreambase from ios, containing a
tstreambuf, then to derive tstream from ostream and tstreambase.

                      ios
      filebuf          |
            |     Tstreambase    ostream
          Tfilebuf        \      /
                           Tstream

However, this was aesthetically unpleasing, and less general than I
would have liked.  I also only redefined the overflow function; I am a
little concerned that some of the other virtual functions might also get
called at some point.

Clearly, in order to get the functionality of an ostream, I need to
inherit from ostream, but since an ostream has data, I can only contain
one ostream; I now have an assymetry between the two ostreams which are
being printed to: one is a base class and one is a member.  Also, why
don't all the output functions defined on ostream resolve to calling a
single function, like "put"?  That surely would have made inheriting
from ostream much simpler.

I confess to not understanding the workings of streams fully, and would
appreciate a brief, higher level description of how to derive from them
than is presented by the man pages.

Thanks.
                                Jak                            jak@cs.brown.edu
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I wouldn't recommend sex, drugs or insanity for everyone, but they've
always worked for me.
                                                       -- Hunter S. Thompson

amb@Apple.COM (A. Michael Burbidge) (01/31/91)

In article <JAK.91Jan31095444@bimini.cs.brown.edu>, jak@cs.brown.edu (Jak Kirman) writes:
> I would like to create a type which behaves precisely as does an ostream
> except that the constructor would take two ostream references, and every
> operation would affect both ostreams equally.
...
> 
> Clearly, in order to get the functionality of an ostream, I need to
> inherit from ostream, but since an ostream has data, I can only contain
> one ostream; I now have an assymetry between the two ostreams which are
> being printed to: one is a base class and one is a member.  Also, why
> don't all the output functions defined on ostream resolve to calling a
> single function, like "put"?  That surely would have made inheriting
> from ostream much simpler.

The ostream class is practically useless as a base class since all of the output
operators are non-virtual. I have asked on this newsgroup before why aren't
the output operators virtual? I received no answers.

Mike Burbidge
Apple Computer, Inc.

the output operators virtual. I got no answers.

dfoster@jarthur.Claremont.EDU (Derek R. Foster) (02/01/91)

In article <JAK.91Jan31095444@bimini.cs.brown.edu> jak@cs.brown.edu writes:
>I would like to create a type which behaves precisely as does an ostream
>except that the constructor would take two ostream references, and every
>operation would affect both ostreams equally.
...
>out how to do this right, but I thought I would first ask if anyone else
>has done this, or anything similar enough to provide either a cookbook
>approach or some hints on what to avoid or worry about.


Well, I fought long and hard with the TC++ iostream.h header file, and
I eventually gained some understanding of it, albeit sketchy in a few
places. 

My goal was to create an ostream type that would output to the console device, 
like cprintf, so I could, for instance, write

con << clear << fore(LIGHTGREEN) << "Green text" << back(LIGHTGRAY)
<< "Gray text";

I eventually succeeded in this, and although my result is more than a little
shaky, (I guessed at what some of the member functions did based on their
names and their return values, since TC++ thoughtfully failed to provide
documentation. Sort of risky.), It seems to work fine for
all the code I've tried it with. I could make this code available if anyone
is interested.

What you want to do is much easier. Keep this in mind: an ostream doesn't
actually do output itself. Instead, it formats the output and then tells
the streambuf associated with it to deal with the output. Thus, if all you
want to do is create an ostream-like object to output to two different
streams, you don't need to derive from ostream at all. What you need to
do is create a streambuf object which will output to two destinations instead
of one. Then, you could simply associate this
new streambuf with a normal ostream, or, if you really want to be able
to use a fancy constructor as you've described, with a class derived
from ostream which simply adds the new constructor, and uses it to
initialize itself as an ostream with one of your new streambufs instead
the normal one it would usually get.

One way would be to have your derived streambuf contain
pointers to two other more normal streambufs. Then you could just define
the relevant member functions of the derived streambuf to call the same
functions in each of the pointed-to streambufs. (Your derived streambuf
can even be unbuffered, if you like, since its buffer won't be used for
anything.)

I.e. to use this, you might eventually be able to do something like:

#include <fstream.h>
class mystreambuf : public streambuf
{
  streambuf * A;
  streambuf * B;
  mystreambuf(streambuf* a, streambuf* b) {A=a;B=b;unbuffered(1););
  int overflow(int = EOF) {A->overflow(); B->overflow();};
  // probably need to redefine all the virtual output functions, like
  // do_sputn() etc. in the same way. I'm not sure what to do about
  // return values. (Probably will have to vary with the function.)
}

filebuf buf1("Out1.dat");
filebuf buf2("Out2.dat");
mystreambuf x(strbuf1, strbuf2);
ostream y(x);
y << "Testing 1.2.3...";


If you're really serious about the constructor being a constructor from
two ostreams, you might try something like:

// define mystreambuf as before

class tstream : public ostream
{
  tstream(ostream& A, ostream& B) 
  : ostream(new mystreambuf(A.rdbuf(),B.rdbuf()) {};
}

ofstream file1("abc.dat");
ofstream file2("def.dat");
tstream both(file1, file2);
both << "Testing 4..5..6";


note 1: A.rdbuf() is simply the value of A's pointer to the streambuf that
it expects to output on.

note 2: This creates a semi-permanent tie between the buffers of the two
original streams and the buffer of the tstream. If you go mucking about
with the buffers of the original streams, you will have an effect on the
tstream. This could be good or bad depending on what you do.

I hope this helps!

Derek Riippa Foster

mat@mole-end.UUCP (Mark A Terribile) (02/03/91)

> > Clearly, in order to get the functionality of an ostream, I need to
> > inherit from ostream, but since an ostream has data, I can only contain
> > one ostream; I now have an assymetry between the two ostreams which are
> > being printed to: one is a base class and one is a member.  Also, why
> > don't all the output functions defined on ostream resolve to calling a
> > single function, like "put"?  That surely would have made inheriting
> > from ostream much simpler.
> 
> The ostream class is practically useless as a base class since all of the output
> operators are non-virtual. I have asked on this newsgroup before why aren't
> the output operators virtual? I received no answers.

First, the operators are not virtual because it would have imposed the
cost of virtual function call on every character, defeating the benefits
of bufferring the output.

Realize that there are two different categories of output on  ostream s.
Output via the insert operator is ``formatted,'' output via the functions
is ``unformatted.'' They former is subject to a variety of control flags and
other goodies; the latter is not.

The key point in the stream is its interface to the streambuf.  I certainly
don't claim that it's intelligible; only that you need to do your magic
stuff BENEATH that interface and inside the streambuf.

What you need is a streambuf whose flush function sends its output to two
other streambufs, those other streambufs representing where you want the
output to go.

It's the streambuf, not the stream, from which you need to derive.
-- 

 (This man's opinions are his own.)
 From mole-end				Mark Terribile