Best practices to remove all errors during c ++ file I / O (or any errors in ios objects)
What are the best techniques to catch all errors during file IO input in C ++? Specifically, what are the best practices to troubleshoot errors that can occur with ios objects? For example, the following program reads a file from disk and prints it:
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <exception>
#include <stdexcept>
// Defines a custom exception
struct MyException : public std::exception {
std::string s;
MyException(std::string s_) : s(s_) {};
const char * what () const throw () {
return ("MyException: " + s).c_str();
}
};
// Prints out nested exceptions
void print_exception(std::exception const & e, size_t const level = 0) {
std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
try {
std::rethrow_if_nested(e);
} catch(const std::exception& e) {
print_exception(e, level+1);
} catch(...) {}
}
// Read the specified filename into a string
std::string read_into_string(std::string const & fname) {
// Create the file stream
std::ifstream fin;
fin.exceptions(std::ifstream::failbit | std::ifstream::badbit);
// Open the file
try {
fin.open(fname.c_str());
} catch(...) {
std::throw_with_nested(MyException(
"Unable to open the file: " + fname));
}
// Make sure to close out the file if there a problem
try {
// Create the string stream
std::stringstream sin;
sin.exceptions(std::ifstream::failbit | std::ifstream::badbit);
// Put the file stream into a string stream
try {
sin << fin.rdbuf();
} catch(...) {
std::throw_with_nested(MyException(
"Error when pusing the file stream into the string stream"));
}
// Turn the string stream into a string
std::string str;
try {
str = sin.str();
} catch(...) {
std::throw_with_nested(MyException(
"Error converting the string stream into a string"));
}
// Close out the file
fin.close();
// Return the string;
return str;
} catch(...) {
// Close out the file
fin.close();
// Rethrow the exception
throw;
}
}
int main() {
try {
std::string str(read_into_string("file.txt"));
std::cout << str;
} catch(const std::exception& e) {
print_exception(e);
}
}
However, it seems very, very hard. Basically, it seems like we should check every time we touch the ios object, as something might go wrong and this will help us know exactly where exactly. In addition, the above code contains multiple file closures, one of which works and one where there is an exception, which is not desirable. Finally, I haven't checked the error status of other ios objects like cout, but technically, since they are ios objects, couldn't they set a bad or bad bit that should be captured? Should string streams be closed on error?
Indeed, the main question is: what are the best practices for troubleshooting errors that can occur with ios objects?
source to share
You can lessen the pain a little because:
-
you can throw exceptions () on the stream after you have opened it
-
stream.close () is implicit in the destructor
-
std::string read_into_string(std::string const & fname) {
// Create the file stream
std::ifstream fin(fname.c_str());
try {
fin.exceptions(std::ifstream::failbit | std::ifstream::badbit);
} catch(...) {
std::throw_with_nested(MyException(
"Unable to open the file: " + fname));
}
// Create the string stream
std::stringstream sin;
try {
sin.exceptions(std::ifstream::failbit | std::ifstream::badbit);
sin << fin.rdbuf();
} catch(...) {
std::throw_with_nested(MyException(
"Error when pusing the file stream into the string stream"));
}
// this try is very pedantic, you probably don't need it since the only exception
// here will be std::bad_alloc
try {
return sin.str();
} catch(...) {
std::throw_with_nested(MyException(
"Error converting the string stream into a string"));
}
// RAII takes care of closing all the file handles for you
}
However, most people will write the function longer:
std::string read_into_string(std::string const & fname) {
try {
std::ifstream fin(fname.c_str());
fin.exceptions(std::ifstream::failbit | std::ifstream::badbit);
std::stringstream sin;
sin.exceptions(std::ifstream::failbit | std::ifstream::badbit);
sin << fin.rdbuf();
return sin.str();
} catch(...) {
std::throw_with_nested(MyException(
std::string("problem with file ") + fname));
}
}
Also, MyException is likely to be more like this:
struct MyException : public std::runtime_error {
MyException(const std::string& s_) : std::runtime_error(std::string("MyException:) +s_) {};
};
Since it comes from runtime_error (which states what it actually is) and you are not returning a dangerous pointer to a temporary in the what () implementation.
source to share
Not recommended .exceptions()
for use for I / O stream in C ++. You most likely learned some other language where they taught you how to use exceptions for everything you can. Do not do that.
It is quite easy to handle errors in streams without exception: the stream will change from true to false. Also, unless you reset the failure bits, any operation in the tampering stream will have absolutely no effect.
Also, there is a way to flush the entire input stream to the output stream.
// Read the specified filename into a string
std::string read_into_string(std::string const & fname) {
// Create the file stream
std::ifstream fin(fname.c_str());
std::ostringstream oss;
oss << fin.rdbuf();
if (!fin) throw MyException();
return oss.str();
}
However, you may need to rethink the need for input in a single stream. I usually find a sequence of lines more useful.
source to share