Is there a good idiom to deal with alternative output streams?
Asked Answered
P

6

2

I want to write a simple program that depending on the options passed it the executable will print the output to the screen or to a file. The program is simple.

#include<iostream>
int main(int argc, char* argv[]){
    ... process options...

    std::ostream& out = ... // maybe std::cout, maybe a *new* std::ofstream;
    out << "content\n";
}

Is there a good idiom to make out refer alternatively to std::cout or a file stream at runtime?

I tried with pointers, but it is horrible. I couldn't avoid using pointers (Not to mention that more ugly code is needed to delete the pointer later).

#include<iostream>
#include<ofstream>
int main(int argc, char* argv[]){

    std::string file = argc>1?argv[1]:"";
    std::clog << "file: " << file << '\n';
    // if there is no argument it will print to screen
    std::ostream* out = (file=="")?&std::cout:(new std::ofstream(file)); // horrible code
    *out << "content" << std::endl;
    if(out != &std::cout) delete out;

}

I don't know, perhaps there is some feature of C++ streams that allows this. Perhaps I have to use some kind of type erasure. The problem, I think, is that std::cout is something that already exists (is global), but std::ofstream is something that has to be created.

I managed to use open and avoid pointers but it is still ugly:

int main(int argc, char* argv[]){

    std::string file = argc>1?argv[1]:"";
    std::clog << "file: " << file << '\n';

    std::ofstream ofs; 
    if(file != "") ofs.open(file); 
    std::ostream& out = (file=="")?std::cout:ofs;
    out << "content" << std::endl;
}
Phonemics answered 19/7, 2016 at 9:46 Comment(4)
Pointers do not necessarily mean dynamic allocation.Herson
Maybe a bit off, but if it is a command line program, you should always output to std::cout and let the user decides if he wants to redirect the output into a file.Domitiladomonic
Possible duplicate of Assigning cout to a variable namePlastometer
This question may be of interest: #24706980Pettus
C
5

My preference is to use streams with suitable stream buffers installed. Here is one way direct output to a file or to std::cout:

#include <iostream>
#include <fstream>
int main(int ac, char* av) {
    std::ofstream ofs;
    if (1 < ac) {
       ofs.open(av[1]);
       // handle errors opening the file here
    }
    std::ostream os(file? file.rdbuf(): std::cout.rdbuf());
    // use os ...
}
Caracul answered 19/7, 2016 at 10:31 Comment(2)
This is clearly the best answer, but I'm the only person to vote on it!Infirmity
This is very neat. It's easy to forget the buffer aspects of streams.Series
I
5

So much over-engineering.

#include <iostream>
#include <fstream>

int main(int argc, char* argv[]) {

    std::ofstream ofs(argc > 1 ? argv[1] : "");
    std::ostream& os = ofs.is_open() ? ofs : std::cout;

    // use os ...
}
Infirmity answered 19/7, 2016 at 10:8 Comment(5)
Nice. but scary. Will ofstream try to open "" or fail to open automatically? . Also, if ofs stream fails for other reasons (not writable file) it will do something unexpected. (Don't get me wrong, I like the solution +1)Phonemics
@alfC, read the documentation of std::basic_ofstream to see exactly how to recognize the issues you mentioned. You really are over-engineering this.Kenton
@alfC, what does "open """ mean? How can you open a file with no name? Have you ever managed to create a file with an empty filename? What do you think happens if you try to open a non-writable file for writing? Hint: it doesn't do anything unexpected. Learn to use your tools instead of writing lots of fragile code to deal with non-issues.Infirmity
1) if there is no filesystem or if it is not writable I wonder if std::ofstream ofs(""); would still try to do something. 2) if `argv[1] == "/readonlylocation", the program will print to the screen instead of giving an error simply because it cannot open the file in write mode (that is the surprising result).Phonemics
1) of course not! 2) ah, ok, unexpected for the user, yes that's true. Dietmar's excellent answer allows checking that easily.Infirmity
C
5

My preference is to use streams with suitable stream buffers installed. Here is one way direct output to a file or to std::cout:

#include <iostream>
#include <fstream>
int main(int ac, char* av) {
    std::ofstream ofs;
    if (1 < ac) {
       ofs.open(av[1]);
       // handle errors opening the file here
    }
    std::ostream os(file? file.rdbuf(): std::cout.rdbuf());
    // use os ...
}
Caracul answered 19/7, 2016 at 10:31 Comment(2)
This is clearly the best answer, but I'm the only person to vote on it!Infirmity
This is very neat. It's easy to forget the buffer aspects of streams.Series
S
2

A runtime binding of the desired stream will pretty much need to look like what you already have.

On the pointer issue, sure you can clean it up a bit... maybe something like this? This is assuming you only want to create the ofstream if the argument exists.

int main(int argc, char* argv[]){ 
    std::string file = argc > 1 ? argv[1] : "";
    std::clog << "file: " << file << '\n';

    // if there is no argument it will print to screen
    std::unique_ptr<std::ostream> fp;
    if (file == "")
        fp = std::make_unique<std::ofstream>(file);

    std::ostream& out = (fp && fp->is_open()) ? std::cout : *fp; // not so horrible code

    out << "content" << std::endl;
}

If the dynamic object is not required, the easiest may be something list this;

int main(int argc, char* argv[]){
    std::string filename = (argc > 1) ? argv[1] : "";

    std::ofstream file(filename);

    // if there is no argument (file) it will print to screen
    std::ostream& out = file.is_open() ? file : std::cout;

    out << "content" << std::endl;
}
Series answered 19/7, 2016 at 9:52 Comment(2)
You don't need dynamic allocation, at all. You try to circumvent it by std::unique_ptrs auto storage, but that's just redundant.Kenton
I know that, the OP had it in the original post, maybe he needed it for some reason... His updates seem to say maybe not...Series
C
1

You could use a shared pointer to a stream for the polymorphic behavior:

#include <memory>
#include <fstream>
#include <sstream>
#include <iostream>

void nodelete(void*) {}

std::shared_ptr<std::ostream> out_screen_stream() { return std::shared_ptr<std::ostream>(&std::cout, nodelete); }
std::shared_ptr<std::ostream> out_file_stream() { return std::make_shared<std::ofstream>(); }
std::shared_ptr<std::ostream> out_string_stream() { return std::make_shared<std::ostringstream>(); }

int main ()
{
    std::shared_ptr<std::ostream> out;

    // case condition:
    out = out_screen_stream();
    out = out_file_stream();
    out = out_string_stream();

    *out << "content" << std::endl;
    return 0;
}

Note: A std::shared_ptr allows managing different possible streams, where some streams should not get deleted (e.g.: std::cout).

Similar, but with std::unique_ptr:

#include <memory>
#include <fstream>
#include <sstream>
#include <iostream>

class Deleter
{
    public:
    Deleter(bool use_delete = true) : use_delete(use_delete) {}

    template <typename T>
    void operator () (const T* p) {
        if(use_delete)
            delete p;
    }

    bool nodelete() const { return ! use_delete; }

    private:
    bool use_delete;
};

using unique_ostream_ptr = std::unique_ptr<std::ostream, Deleter>;
unique_ostream_ptr out_screen_stream() { return unique_ostream_ptr(&std::cout, false); }
unique_ostream_ptr out_file_stream() { return unique_ostream_ptr{ new std::ofstream }; }
unique_ostream_ptr out_string_stream() { return unique_ostream_ptr{ new std::ostringstream  }; }

int main ()
{
    unique_ostream_ptr out;

    // case condition:
    out = out_screen_stream();
    out = out_file_stream();
    out = out_string_stream();

    *out << "content" << std::endl;
    return 0;
}
Ciaphus answered 19/7, 2016 at 10:25 Comment(0)
P
1

I often use something like this for command-line tools:

int main(int, char* argv[])
{
    std::string filename;

    // args processing ... set filename from command line if present
    if(argv[1])
        filename = argv[1];

    std::ofstream ofs;

    // if a filename was given try to open
    if(!filename.empty())
        ofs.open(filename);

    // bad ofs means tried to open but failed
    if(!ofs)
    {
        std::cerr << "Error opeing file: " << filename << '\n';
        return EXIT_FAILURE;
    }

    // Here either ofs is open or a filename was not provided (use std::cout)

    std::ostream& os = ofs.is_open() ? ofs : std::cout;

    // write to output
    os << "Some stuff" << '\n';

    return EXIT_SUCCESS;
}
Pettus answered 19/7, 2016 at 10:52 Comment(0)
L
-4

Maybe a reference?

#include<iostream>
#include<ofstream>


int main(int argc, char* argv[])
{
    auto &out = std::cout;
    std::ofstream outFile;

    std::string fileName = argc>1?argv[1]:"";

    std::clog << "file: " << file << '\n';
    // if there is no argument it will print to screen
    if(!fileName.empty())
    {
        outFile.open(fileName);
        out = outFile;
    }

    out<<"one, one, two";

    return 0;
}
Leifeste answered 19/7, 2016 at 9:54 Comment(1)
Reference can not be reassigned, streams neither.Herson

© 2022 - 2024 — McMap. All rights reserved.