Although the other answers are valid and useful, I think the real reason is simpler.
The iostreams design is much older than a lot of the Standard Library, and predates wide use of exceptions. I suspect that in order to be compatible with existing code, the use of exceptions was made optional, not the default for failure to open a file.
Also, your question is only really relevant to file streams, the other types of standard stream don't have open()
or close()
member functions, so their constructors don't throw if a file can't be opened :-)
For files, you may want to check that the close()
call succeeded, so you know if the data got written to disk, so that's a good reason not to do it in the destructor, because by the time the object is destroyed it is too late to do anything useful with it and you almost certainly don't want to throw an exception from the destructor. So an fstreambuf
will call close in its destructor, but you can also do it manually before destruction if you want to.
In any case, I don't agree that it doesn't follow RAII conventions...
Why did the library designers choose their approach over having opening only in constructors that throw on a failure?
N.B. RAII doesn't mean you can't have a separate open()
member in addition to a resource-acquiring constructor, or you can't clean up the resource before destruction e.g. unique_ptr
has a reset()
member.
Also, RAII doesn't mean you must throw on failure, or an object can't be in an empty state e.g. unique_ptr
can be constructed with a null pointer or default-constructed, and so can also point to nothing and so in some cases you need to check it before dereferencing.
File streams acquire a resource on construction and release it on destruction - that is RAII as far as I'm concerned. What you are objecting to is requiring a check, which smells of two-stage initialization, and I agree that is a bit smelly. It doesn't make it not RAII though.
In the past I have solved the smell with a CheckedFstream
class, which is a simple wrapper that adds a single feature: throwing in the cosntructor if the stream couldn't be opened. In C++11 that's as simple as this:
struct CheckedFstream : std::fstream
{
CheckedFstream() = default;
CheckedFstream(std::string const& path, std::ios::openmode m = std::ios::in|std::ios::out)
: fstream(path, m)
{ if (!is_open()) throw std::ios::failure("Could not open " + path); }
};
throw_exception
mode value. It is possible to set exceptions for later operations, but throwing constructor would be better. – Dissolute