I want to do input from std::cin, or from the file specified in argv[1]. How
do I do this in idiomatic C++?
Here's what I want to do in C:
1 2 3 4 5 6 7 8
int main(int argc, char *argv[])
{
FILE *f = stdin;
if (argc == 2) f = fopen(argv[1], "r");
if (!f) handleError();
// do something with f
}
Here's what I've got in C++. I actually want to construct an object foo,
which is of class Foo. The code below works (if I didn't make a typo) but
it requires foo to be constructed first with the default constructor. Is
there some more idiomatic way to do this in C++?
1 2 3 4 5 6 7 8 9 10 11
int main(int argc, char *argv[]) {
Foo foo;
if (argc == 2) {
std::ifstream f(argv[1]);
if (!f) handleError();
foo = Foo(f);
} else {
foo = Foo(std::cin);
}
// do something with foo
}
I was thinking something similar, another over-engineered solution :)
(I like helios' solution)
The point here is that it hides the actual need for the end user to have a separate ifstream object. The user just needs to care about the stream, and RAII takes care of the ifstream.
#include <iostream>
#include <fstream>
struct Foo
{
// use the streambuffer as the associated stream buffer of the input stream
explicit Foo( std::streambuf* stmbuf ) : stm(stmbuf) {}
std::istream stm ; // read input from this input stream
// ...
};
int main( int argc, char* argv[] )
{
static std::filebuf fbuf ;
if( argc == 2 ) fbuf.open( argv[1], std::ios::in ) ; // try to open the file for input
// if the file was successfully opened, use the file buffer, otherwise use stdin's buffer
Foo foo( fbuf.is_open() ? std::addressof(fbuf) : std::cin.rdbuf() ) ;
// ...
}
The type of the stream stm is std::istream; its destructor would not destroy the associated streambuffer.
This destructor does not perform any operation on the underlying streambuffer (rdbuf()): the destructors of the derived input streams such as std::basic_ifstream and std::basic_istringstream are responsible for calling the destructors of the streambuffers. https://en.cppreference.com/w/cpp/io/basic_istream/~basic_istream
Thank you everyone for your replies. They have given me much food for thought. I really appreciated how we all seemed to be on the same wavelength, responding to issues that we didn't even discuss.
First of all, in my original C++ implementation I was bothered by the wasted construction of foo. It wasn't just that, but why should I have to add a default constructor to Foo just because the caller can't figure out how to specify a proper istream. Also, the implementation of Foo is elegant. It doesn't care where the istream comes from--it just uses it. So I didn't want to put any of the {file,standard input} code in Foo. Who knows: I might even want to initialize it using a stringstream.
So helios's original solution makes a lot of sense. I write a lot of little filter programs, and I've used the paradigm in my C code quite a few times. His solution is very close to a transliteration of that C code. I could easily see using it in a bunch of little C++ programs. The only drawback (and it is really tiny) is that there is a temporary variable whose scope doesn't end when the variable is no longer needed. I think that this unspoken defect was the driver for all the (overengineered :-)) solutions which I am still studying.
JLBorges solution was great, because it enabled me to learn more about C++ I/O. In fact, in my current project, I may just use the streambuf pointer directly to construct foo. I only use std::istream:read. I could replace those occurrences with std::streambuf::sgetn.