Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices for catching all errors during C++ file IO (or any errors in ios objects)

What are the best practices for catching all errors during file IO in C++? More specifically, what are the best practices for dealing with errors that could potentially arise 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's 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 heavy. Basically, it seems like we have to check every time we touch an ios object since something could go wrong and it would help to know exactly where. In addition, the above code contains multiple file closes, one what everything works and one where there's an exception, which is undesirable. Finally, I didn't check the error status of the other ios objects such as cout, but technically since they're ios objects, couldn't they set a bad or fail bit as well that should be trapped? Do the string streams need to be closed out in case of an error?

Really, the core question is: what are the best practices for dealing with errors that could potentially arise with ios objects?

like image 200
wyer33 Avatar asked Oct 06 '14 19:10

wyer33


People also ask

How errors and exceptions are handled in C?

The C programming language does not support exception handling nor error handling. It is an additional feature offered by C. In spite of the absence of this feature, there are certain ways to implement error handling in C. Generally, in case of an error, most of the functions either return a null value or -1.

What is error handling in C?

In C/C++, the library function ferror() is used to check for the error in the stream. Its prototype is written as: int ferror (FILE *stream); The ferror() function checks for any error in the stream. It returns a value zero if no error has occurred and a non-zero value if there is an error.

Does C support exception handling?

C does not provide direct support for error handling (also known as exception handling). By convention, the programmer is expected to prevent errors from occurring in the first place, and test return values from functions.

Which library do we include for supporting file operations?

In C++, fstream library is used to handle files, and it is dealt with the help of three classes known as ofstream, ifstream and fstream.


2 Answers

It's not common practice to enable .exceptions() on an I/O stream in C++. Most likely you learned some other language where they taught you to use exceptions for everything you can. Don't.

It' perfectly easy to handle errors on streams without exceptions: the stream will change from being truthy to being falsy. Additionally, unless you reset the failure bits, any operation on a falsy stream will have absolutely no effect.

Additionally, there is a method for dumping an entire input stream into an 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 might want to rethink your need for input in a single stream. Usually I find a sequence of lines to be much more useful.

like image 144
o11c Avatar answered Sep 28 '22 20:09

o11c


You can reduce the pain a little because:

  1. you can call exceptions() on the stream after you've opened it

  2. 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 would write the function more like this:

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)); 
    }
}

in addition, MyException should probably be more like this:

struct MyException : public std::runtime_error {
    MyException(const std::string& s_) : std::runtime_error(std::string("MyException:) +s_) {};
};

Because this way it's derived from runtime_error (which states what it actually is), and you're not returning a dangerous pointer into a temporary in the what() implementation.

like image 25
Richard Hodges Avatar answered Sep 28 '22 22:09

Richard Hodges