2

If I want to load an object from a file, there are a number of things that can go wrong. Thus, one needs a way of handling errors when doing so. In some languages, like haskell, one can return a Maybe object that might contain the newly created object. In C++, although it is in principle possible to create such a convenience class, it is usually not the most straight-forward way to do things. Below I list various ways errors could be handled when loading an object from a file in C++.

  • Return a pointer:

Object* load_from_file(const char* filename);

By returning a pointer, we can return nullptr in case a problem occured while loading the object.

  • Take a reference to a default constructed object and return false if a problem occured:

bool load_from_file(const char* filename, Object& obj);

Using this method, a separate initialization method needs to be implemented for the object, which will be called after loading it from the file.

  • Throw an exception inside the load_from_file function:

Object load_from_file(const char* filename);

Generally, I believe, throwing an exception in such a case might be bad, as this is not really an "exceptional" case.

  • Load data to an intermediate struct:

struct ObjectData{
    //data
};
bool load_from_file(const char* filename, ObjectData& data);
//example use:
ObjectData data;
if(load_from_file("path_to_some_file", data)){
    Object(data);
    //etc...
}

The difference here with the second method is that you do not need to create an initialization method for Object.

Generally, what is the preferred method for the problem described above, or which method is preferred in which cases?

2
  • 1
    I am not sure what the accepted best practice is here, but I would consider using std::unique_ptr as a way to ensure proper memory management while also allowing a nullptr return value.
    – user22815
    Dec 2, 2014 at 15:08
  • What you and other languages call maybe is known as optional in the C++ world. boost has a battle-tested implementation and Library TS1 has it, with the hope that it will be in C++17 Dec 2, 2014 at 17:28

2 Answers 2

3

Don't confuse "exception" with "rare"

The main point of exceptions and exception handling is keeping different types of code apart:

  • The code that provides the main functionality (the "happy path" code)
  • Everything else: The code that handles all those 973 other cases where not everything has worked out wonderfully and precisely to your main functionality's liking.

Pulling the code for the not-quite-happy statements out of your happy path code makes the latter nicely simple and understandable. That's what exception handling is about, really (plus keeping the other 973 cases apart from one another as well; plus making sure error handling of errors that occurred during error handling will happen reliably as well).

So your formulation "there are a number of things that can go wrong" is a good indication that an exception is probably the most maintainable-in-the-long-run approach for your case. And that remains true even if the exceptions are more frequent than the happy path case.

3
  • 1
    Exceptions are used differently in Python and in C++, and “it is easier to ask for forgiveness than for permission” does not apply everywhere. Properly deallocating resources when exceptions are encountered can be difficult (note that C++ doesn't have garbage collection like Python), and some people have just given up writing exception-safe C++ code (!). Furthermore, the design of some languages (such as C++) makes exceptions very expensive, so they have to be avoided in performance-critical sections anyway.
    – amon
    Dec 2, 2014 at 15:40
  • 2
    If you use RAII correctly and no raw pointers, there is no problem you can get into with exceptions
    – Nikko
    Dec 2, 2014 at 16:04
  • This is just a personal preference, but I try to keep my functions as pure as possible. That's why I use exceptions only for "exceptional" cases. Dec 3, 2014 at 12:53
3

Generally, what is the preferred method for the problem described above, or which method is preferred in which cases?

The prefered method looks like this:

class Object { ... };

std::istream& operator>>(std::istream& in, Object& obj) { /* classic "in" operator*/ }

Client code (1):

Object o;
if(std::cin >> o) {
    // do something with o
}

Client code (2):

Object o;
std::cin.exceptions(std::ios::failbit);
std::cin >> o; // will throw on failure
// do something with o

It is up to your implementation of the input operator, to set the failbit for in (which may or may not throw an exception), in case the values read from the stream are valid for the data types, but not valid for being placed into the Object instance.

This is prefered, because:

  • it is flexible wrt the stream type (with this code, you can just as well use a std::stringstream instance and serialize your data to a string)
  • it doesn't impose the error handling strategy (client code gets to decide if an exception should be thrown or not)
  • it minimizes knowledge cf. Law of Demeter (the loading function doesn't need to know what a file - or a file path - is)
  • it integrates and scales properly with the built-in C++ i/o support; that means, you can easily do this:

    Object a;
    int b;
    std::string word;
    
    std::cin >> a >> b >> word; // a is read the same as any int or std::string
    

Downsides: - unless you are careful on reading, errors may slip by (consider my first example, without the if as an example).

Edit: An extra advantage of writing i/o code with i/o-stream operators, is that you get free integration with other code that uses std::i/o-streams. For example, after you define your operator, you could use boost::lexical_cast with Object instances:

#include <boost/lexical_cast.hpp>

std::ostringstream buffer;
Object a = { /* data here */ };
buffer << a;
auto b = boost::lexical_cast<Object>(buffer.str());
1
  • So, if I understand correctly, you suggest that the second method I listed is the preferred one, only you propose using STL streams instead? Dec 2, 2014 at 17:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.