Jump to content
  • Advertisement

Archived

This topic is now archived and is closed to further replies.

[C++] Composite std::ostream's

This topic is 5640 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey, all - it''s been awhile. Here''s a C++ standard library meets Design Patterns vexer. It''s a fun problem, but I just can''t figure it out. If you consider yourself pretty good with the details of C++, the standard library, and the composite design pattern, give this one a try the next time you''re bored. Implement a composite stream template object that can be used anywhere any other stream object of the same template parameters can. Composite here is the standard GoF composite pattern. "Any other stream object" means that if the composite stream is an o-stream type composite, it can directly replace any other o-stream type. That goes for the i-stream types and io-stream types, too. It''s not as trivial as I''d first thought, so far as I can tell. This is especially due to the fact that the only virtual method in all of the basic_streams is their destructor. I ran into this problem working on a debug library. One of my goals in the library is to simultaneously output text to various destinations, such as to a file and to an edit window, using a single statement of the form: some_ostream << "Some text to be output." << some_object << endl; where some_object is an object that has it''s insertions operators defined and some_ostream is a single object that can replace any ostream or, more generally, any basic_ostream<> object. I''ve tried a few different ideas to pull this off this week, and I''ve had a lot of fun trying, but I''m just not managing it yet. I don''t want to bias (confuse) anyone with the thought processes behind my failed attempts yet, so I''m not posting them until Monday. Thank you for your bandwidth, and good luck! -- Succinct

Share this post


Link to post
Share on other sites
Advertisement

    
#include <iostream>
#include <string>
using namespace std;

class cool_stream:public ostream
{
public:
cool_stream(ostream& stream1,ostream& stream2):
ostream(0),s1(stream1),s2(stream2)
{
}
template<class object>
ostream& operator<<(object o)
{
s1<<o;
s2<<o;
return *this;
}
private:
ostream& s1;
ostream& s2;
};

struct mystruct
{
string a;
int b;

};
ostream& operator<<(ostream& out,const mystruct t)
{
return out<<t.a<<t.b<<endl;
}

int main()
{
cool_stream c(cout,cerr);
mystruct m;
m.a = "hi there";
m.b = 54;
c<<m<<endl;

}


[edited by - sjelkjd on December 6, 2002 8:44:07 PM]

Share this post


Link to post
Share on other sites
Well, sjelkjd, that''s close, but it won''t work in all situations.

For instance, consider


  
cool_stream cool( cout,clog );
ostream& not_very_cool = cool;

not_very_cool << "this is calling ostream::operator <<( char* )." << std::endl;


Cool_stream WILL work if you are using a statically typed cool_stream, but if you''re using polymorphism, as many functions requiring an ostream do, you''re hosed.

That was my first try last week, just to make sure it would work that way, but then you run into the polymorphism problem. I''m of the opinion, now, that this isn''t going to be possible using std::ostream as a base class.

I next tried deriving from basic_streambuf, because it does have a good number of virtual methods to override, but this gets messy for a two reasons:
1) now you must assemble streams, there is no simple constructor.
2) some of the non-virtual interface functions of the streambuf class are not simple interface functions, such as sputc(), so workarounds are needed that abuse the implementation, such as forcing overflow() to be called by setting a null next put pointer.

I still think that if there''s going to be a way to pull this off polymorphically, it''ll be by deriving a streambuf class, possibly one that wraps not only composing streambufs, but possibly even whole i/o/iostream classes...

It''s starting to hurt my head, now, though, and I''ve got other work to do. Any further suggestions will be appreciated, though I''ll not be adding any for a few days while I finish a quick project I need for work.

Thank you all for your bandwidth,
-- Succinct

Share this post


Link to post
Share on other sites
Well, here''s a thought. Might not be as elegant as you want, but...
Construct one global cool_stream. Have a member function which will set the mode, and you can specify whatever streams you want to write to, eg:

  
class cool_stream :public ostream
{
int mode;
vector<ostream*> streams;

public:
cool_stream():mode(0) {}
int AddStream(ostream* stream)
{
streams.push_back(stream);
return streams.size()-1;
}
void SetMode(int flags)
{
mode = flags;
}
void ActivateStream(int index)
{
mode |= 1<<index;
}
void DeactivateStream(int index)
{
mode &= ~(1<<index);
}
template<class type>
cool_stream& operator<<(type object)
{
int flags = 1;
for(int i=0;i<streams.size();i++)
{
if(flags & mode)
(*streams[i])<<object;
flags <<= 1;
}
return *this;
}
};

Then, inside your code you could do:

  
const int COUT = 1<<0;
const int CERR = 1<<1;
//etc...


//make this global

cool_stream log;
log.AddStream(cout);
log.AddStream(cerr);

//in files

log.SetMode(COUT | CERR);
log<<someStuff;
log.SetMode(COUT);
log<<moreStuff;


Not the most elegant of solutions, especially if you need to use different streams, but something to think about.

Share this post


Link to post
Share on other sites
Hey - that''s actually not too bad of an idea.

True, it''s not as elegant and as general as I was hoping, but then, I say to myself, "IT''S FOR FREAKING DEBUGGING!"

All things considered, I think that''s probably the best route to go. Good thinking, bud - think out of the box.

Thanks, you''ve helped me make good use of my bandwidth. Happy coding, all!

-- Succinct 8Þ

Share this post


Link to post
Share on other sites
Actually, there is an easy solution to your problem. What you want to look into is std::stream_buf.
You can look at libGDN for some examples in the IO subdirectory, especially the internal_buffer member in the HTMLstream. To be precise,create an subclass of std::stream_buf, overload sync() and ...I think overflow(), call setp (I think) in your constructor and have a static char buf[some_size]. So anyway, in sync() you just call some_stream << buff; That way you even keep locales, so it would localize and do other cool stuff for ya too. I'm not sure about error handling, tho. I'm going to go fix the libGDN way of doing to this new way. And add a N-forwarder class.

You know, our io debug system doesn OutputDebugString, HTML output, and soon to come Console output. *plug plug*

EDIT: Oh, and you would create a subclass of ostream which would hold an NFOrwarder_buff and in the constructor call ostream(&buff). That way the buffer is linked up to the ostream.
Look at the code for more informatino. I can't describe it in detail, I'm supposed to be writing a college essay right now due tomorrow.

EDIT2: I see you've already attempted using a basic_streambuff derivative. Well, it's not really that bad. Focus on two virtual functions: sync() and overflow(). In sync(), simply do << on the char buffer to each forwarded stream. It shoudl work, I don't see what objections there are...
-----------------------------
Gamedev for learning.
libGDN for putting it all together.

[edited by - risingdragon3 on December 9, 2002 11:59:49 PM]

[edited by - risingdragon3 on December 9, 2002 12:02:44 AM]

Share this post


Link to post
Share on other sites
Well, one of the problems with driving from basic_streambuf is that it handles some requests internally, such as sputc, under certain conditions, only calling the virtual functions under almost exceptional conditions.

sputc will manually modify the buffer and next character pointer if there is a next character pointer without calling any of the virtual methods.

You can circumvent this by manually setting the next pointer to null, via setp( pbase(),0,epptr() ), but then that gets goofy under the MS standard library implementation, because epptr() is implemented as pptr() + offset, the offset being the distance from the current pptr() (which is 0, due to setp above), to the end of the buffer. The documentation also says that a requirement is to do equivalence tests such that pbase() < pptr() < epptr(). I''m not sure how bad these two things get in actual production code. I''ve never actually managed to get sputc called, xsputn being called generally, but I''m sketchy on implementing against the standard doc even if it does work under MS''s bunk implementation.

Anyway, I''m at home right now, and have none of my code to look at, however, I''ll take a look at that stuff tomorrow. Thanks for the heads up.

Oh, and though I''ll probably not actually use libGDN, as I detest all 3rd party packages that I absolutely don''t need, I''ll take a look at the code (which I''m assuming is open source, since you''re telling me I should ), and I''ll make notes of it anytime I use the code I derive from it.

Good looking out, guys (gals?), thanks a lot.


-- Succinct

Share this post


Link to post
Share on other sites
quote:
Original post by Succinct
sputc will manually modify the buffer and next character pointer if there is a next character pointer without calling any of the virtual methods.


What''s the problem with that?
So long all the writing is done in sync(), you''ll be good...say the sputc() messes with the internal buffer. We dont'' care, because when that internal buffer is full - we out it, so ... it''s okay. The output is correect even if sputc is called.
Maybe I''m misunderstanding you.

quote:

Oh, and though I''ll probably not actually use libGDN, as I detest all 3rd party packages that I absolutely don''t need, I''ll take a look at the code (which I''m assuming is open source, since you''re telling me I should ), and I''ll make notes of it anytime I use the code I derive from it.


That''s what libGDN is also there for. Have a look around, see what you can get out of it...

-----------------------------
Gamedev for learning.
libGDN for putting it all together.

Share this post


Link to post
Share on other sites
OH DUH!

Now I gotcha - have the stream write to its own normal internal buffer (or my own), then write that buffer to the composing streams on sync... It was I who wasn''t understanding correctly.

That''ll work - problem solved.

Thanks!

-- Succinct

Share this post


Link to post
Share on other sites

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!