Reputation: 929
I would like to implement a class with the help of RAII. The resources should be acquired in the constructor, but it's possible that the acquistition failed. I'll give an example in the following using FILE:
class file {
public:
file(const char* filename) {
file_ = fopen(filename, "w+");
if(!file_) {
// Okay
}
else {
// ERROR
}
}
~file() {
if (fclose(file_)) {
// ERROR
}
}
void write(const char* str) {
if (EOF == fputs(str, file_)) {
throw runtime_error("file write failure");
}
}
private:
FILE* file_;
};
So, what's the best way to handle an error which is occurred if fopen returns NULL? Because it's the constructor I can't return also NULL.
I hope somebody can give me a hint how to handle such errors!
Thank you, best regards,
Flasher
Upvotes: 2
Views: 1746
Reputation: 56956
The only way a constructor can report failure is by throwing an exception.
Destructors, to the contrary, must not throw exceptions (if a destructor throws during stack unwinding, std::terminate
is called, which ends the program by default).
If destruction fails, you can
If you use RAII correctly, exceptions can traverse your code without damage.
Example here:
#include <cerrno>
#include <cstring>
#include <sstream>
file::file(const char* filename)
{
file_ = fopen(filename, "w+");
if (!file_)
{
std::ostringstream os;
os << "Cannot open " << filename << ": "
<< std::strerror(errno);
throw std::runtime_error(os.str());
}
}
file::~file()
{
fclose(file_);
}
Note that this code has many bugs: the fclose
function can fail, and the failure may or may not be related to the closing (eg. some write errors are only reported when flushing at the close
system call on POSIX systems). Please use iostreams for file I/O in C++, since they provide convenient abstractions over those concerns.
Upvotes: 6
Reputation: 153929
Despite the name, RAII doesn't necessarily involve resource aquisition;
std::shared_ptr
doesn't do the new
, for example. The name is
historical, and the pattern really concerns cleanup.
In the case of File
, of course, the "correct" answer is to use
std::ifstream
and std::ofstream
, and be done with it. And very
important (at least for output): RAII can only be used for exceptional
clean-up, since you have to verify that the close
has correctly
finished before leaving the block in the normal case. As a general
rule, if the close fails, you want to delete the file (and return
EXIT_FAILURE
from main
, or do something that will make the error
apparent, and prevent further code from executing on the assumption that
the data were written). So the only time it would be acceptable to
defer the close
to a destructor is in a case where you've already
found an error, and are going to delete the file as part of cleanup
anyway.
Generally, output obeys a transaction model, more than an RAII one; I
tend to wrap my std::ofstream
in an OutputFile
class, with a
commit()
function which closes the file, and marks the class as
committed if (and only if) the close succeeds; if the destructor is
called and the file hasn't been committed, the destructor closes the
file, then deletes it. (Presumably some higher level will catch the
exception and convert it into a return EXIT_FAILURE
in main.)
Also, IO is a bit exceptional in general. Normally, if you cannot create an object, for whatever reason, the constructor should raise an exception; you don't want "zombie" objects floating around, which can't be used. In the case of IO, however, you have to deal with the fact that the object can become invalid and unusable after construction, even when construction succeeds. Since you have to constantly check their status anyway (after each read on input, and after close on output), it's often appropriate to just set some internal flag in the object, which you test.
Upvotes: 1