Use multiple ofstreams to write to a single output file in c++
Asked Answered
B

2

7

I have class Writer that has two ofstream members.
Both streams are associated with the same output file. I'd like to use both streams in Writer::write method, but to make sure that each stream writes to the end of the real output file.

Code

class my_ofstream1:
    public ofstream
{
    // implement some functions.
    // using internal, extended type of streambuf
};

class my_ofstream2:
    public ofstream
{
    // implement some functions.
    // using internal, extended type of streambuf 
    // (not the same type as found in my_ofstream1)
};


class Writer
{
public:

    void open(string path)
    {
        f1.open(path.c_str(),ios_base::out); f2.open(path.c_str(),ios_base::out);
    }

    void close()
    {
        f1.close(); f2.close();
    }

    void write()
    {
        string s1 = "some string 1";
        string s2 = "some string 2";
        f1.write(s1.c_str(), s1.size());

        // TBD - ensure stream f2 writes to the end of the actual output file
        assert(f1.tellp() == f2.tellp());
        f2.write(s2.c_str(), s2.size());
    }

private:
    my_ofstream1 f1;
    my_ofstream1 f2;
};

void main()
{
    Writer w;
    w.open("some_file.txt");
    w.write();
    w.close();
}

Questions

How to ensure f2 is in sync with f1? meaning, before writing, stream offset of f2 must be in sync with stream offset of f1 and vice versa?
I can't use function std::ios::rdbuf since each ofstream uses special derived streambuf. so by using rdbuf() I'll lose the necessary functionality.
I tried using some of the techniques found in Synchronizing Streams topic but could not make it happen.

Breathing answered 24/12, 2013 at 7:48 Comment(7)
Look into tie(). It allows you to couple a stream (input or output) to an output stream, such that the output of the tied stream gets flushed. In my quick reading of tie(), though, it appears the synchronization is only one-way. Otherwise, it seems like you might consider an approach that funnels everything through a common class that serializes and writes everything via the true ofstream.Offwhite
Is the "internal, extended type of streambuf" the same type for both streams? If so, just create both streams writing to the same streambuf object.Thumbscrew
Unfortunately, they are not of the same type. Of course, they are both of type streambuf but I must have two different objects since each special streambuf manipulate the data in a different way before writing it to the file.Breathing
Ultimately, all variants must serialize down to a stream of bytes that get written to the file. Can you make your various streambufs do that, and then send their payloads to a single, shared ofstream after serialization? I see no reason to push the actual file I/O that far up in the hierarchy.Offwhite
Well, I agree the data manipulation is too far up in the hierarchy. but I would really like to address both ofstreams as a black box which I cannot modify. Once we exhaust all proposed solutions with no luck I guess I'll have to redesign.Breathing
@JoeZ There's no problem at the ostream level. It's at the streambuf level that he has to delegate: his custom streambufs should be decorators of a common final filebuf (and not do any buffering themselves).Cartagena
Upon reflection, I think that James, the authority, is correct - you will need to share the filebuf, except change the attach functions in my answer so that they attach your own custom streambufs. The streambufs will have to contain a pointer to the filebuf so that they can do their processing and then delegate to filebuf's sputc, etcEdee
C
1

This looks like both of your classes use the filtering streambuf idiom. In any case, don't derive your classes from std::ofstream, but directly from ostream, and have them both use the same std::filebuf object. If you are using the filtering streambuf idiom, don't let your filtering streambuf's buffer; leave that to the final std::filebuf.

In other words, your "internal, extended type of streambuf" should contain a pointer to the final sink, which will be a filebuf (but your filtering streambufs don't need to know this). Functions like sync, they just pass on to the final destination, and they should never establish a buffer themselves, but pass everything on to the filebuf.

Cartagena answered 24/12, 2013 at 9:33 Comment(0)
E
1

Is this not what you are looking for? This could be easily modified to work with ostreams rather the ofstreams, which is nicer - the actual issue is synchronisation of the buffers. In this code I have simply made the filebuf bf unbuffered and it works fine. Alternatively leave it buffered but include calls to pubsync when switching between my_ofstream's. I don't understand why ios:rdbuf is not available. Are you creating your own streambuf?

#include <iostream>
#include <fstream>
#include <assert.h>

using namespace std;

class my_ofstream1 : public ofstream
{
public:
    my_ofstream1& write (const char_type* s, streamsize n)
    {
        ofstream::write (s, n);
        //rdbuf()->pubsync();
        return *this;
    }

    void attach (filebuf* bf){
        ios::rdbuf(bf);
    }
};

class my_ofstream2 : public ofstream
{
public:
    my_ofstream2& write (const char_type* s, streamsize n)
    {
        ofstream::write (s, n);
        //rdbuf()->pubsync();
        return *this;
    }

    void attach (filebuf* bf){
        ios::rdbuf(bf);
    }
};


class Writer
{
    filebuf bf;
    my_ofstream1 f1;
    my_ofstream1 f2;

public:

    void open(string path)
    {
        bf.open(path.c_str(),ios_base::out);
        bf.pubsetbuf(0,0); //unbufferred
        f1.attach(&bf); f2.attach(&bf);
    }

    void close()
    {
        f1.close(); f2.close();
    }

    void write()
    {
        string s1 = "some string 1";
        string s2 = "some string 2";
        f1.write(s1.c_str(), s1.size());

        assert(f1.tellp() == f2.tellp());
        f2.write(s2.c_str(), s2.size());
    }


};

int main()
{
    Writer w;
    w.open("some_file.txt");
    w.write();
    w.close();

    return 0;
}   
Edee answered 24/12, 2013 at 9:32 Comment(5)
I assume James' answer is wrong too if each use a special stream_buf. Otherwise just use rdbuf to access the stream_buf and pubsetbuf with 0 as arguments to make it unbuffered.Edee
10x. As I wrote, I can't use function std::ios::rdbuf since each ofstream uses special derived streambuf. so by using rdbuf() I'll lose the necessary functionality. long story short, if both stream are using the same streambuf then both streams are have the same writing functionality.Breathing
ahh so we are both wrong - I'll see what I can dig up. In either case rdbuf should be there, just overload it for your streambuf. What do yuo mean by derived? Are they specialisations of basic_streambuf? Or do they inherit from some streambuf object?Edee
They inherit form streambuf object and implement such function as open(),close(), overflow(), underflow(), etc. 10x for your time. I appreciate it.Breathing
They just need constructors like my_streambuf(strembuf* b) and then after processing intput, write it on to b (which will be the shared filebuf) problem solved, good luck :)Edee
C
1

This looks like both of your classes use the filtering streambuf idiom. In any case, don't derive your classes from std::ofstream, but directly from ostream, and have them both use the same std::filebuf object. If you are using the filtering streambuf idiom, don't let your filtering streambuf's buffer; leave that to the final std::filebuf.

In other words, your "internal, extended type of streambuf" should contain a pointer to the final sink, which will be a filebuf (but your filtering streambufs don't need to know this). Functions like sync, they just pass on to the final destination, and they should never establish a buffer themselves, but pass everything on to the filebuf.

Cartagena answered 24/12, 2013 at 9:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.