gambr
gambr

Reputation: 199

std::ostream class member variable to be used as std::ofstream or std::ostringstream

I'm struggling with streams. I would like to have FileWriter that writes MyObject data to file or to a stringstream. I don't want the interface of write() to take the file path or the stringstream becouse I may have other concrete writers that serialize to some different devices (i.e. socket). So I need to pass the file path or the stringstream to the FileWriter constructor. I need to have a stringstream as an alternative to a filePath so that I can easily implement unit testing.

Here is a sketch of the classes I have described:

class IObjectWriter {
virtual void write(MyObject& o) = 0;    
};

class FileWriter : public IObjectWriter {

FileWriter(const std::string& filePath) {
    std::ofstream os(filePath, std::ios::out);
    // assign in some way os to stream member variable
}

FileWriter(std::ostringstream& so) {
    // assign in some way os to stream member variable
}

void write(MyObject& o) override {
    stream << o.getSomeValue();
}   

private:
    std::ostream stream;    
};

The problem I could not solve is to "assign" the ofstream and ostringstream to the ostream member so that the write() call can write independently on file or stingstream.

Problems I've run into:

Possible solutions or ideas:

Upvotes: 0

Views: 749

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 596497

Why can't you simply have two separate classes - FileWriter that writes to an ofstream, and StringWriter that writes to an ostringstream? Let the user decide which type of writer it wants to use, eg:

class FileWriter : public IObjectWriter {
public:
    FileWriter(const std::string& filePath) : stream(filePath) { }

    void write(MyObject& o) override {
        stream << o.getSomeValue();
    }   

private:
    std::ofstream stream;
};

class StringWriter : public IObjectWriter {
public:
    StringWriter(std::ostringstream &os) : stream(os) { }

    void write(MyObject& o) override {
        stream << o.getSomeValue();
    }   

private:
    std::ostringstream &stream;
};
FileWriter fw("path");
fw.write(obj);

std::ostringstream oss;
StringWriter sw(oss);
sw.write(obj);

You could then reuse some code by adding a common base class, eg:

class StreamWriter : public IObjectWriter {
public:
    StreamWriter(std::ostream& os) : stream(os) { }

    void write(MyObject& o) override {
        stream << o.getSomeValue();
    }   

private:
    std::ostream &stream;
};


class FileWriter : public StreamWriter {
public:
    FileWriter(const std::string& filePath) : StreamWriter(ofs), ofs(filePath) { }

private:
    std::ofstream ofs;
};

class StringWriter : public StreamWriter {
public:
    StringWriter(std::ostringstream &oss) : StreamWriter(oss) { }
};

But, in that case, you may as well just use StreamWriter by itself, and let the user pass in whatever ostream-derived object it wants to write to, eg:

std::ofstream ofs("path");
StreamWriter sw1(ofs);
sw1.write(obj);

std::ostringstream oss;
StreamWriter sw2(oss);
sw2.write(obj);

Upvotes: 3

Related Questions