debugcppMinor
C++ alternative to exceptions
Viewed 0 times
exceptionsalternativestackoverflow
Problem
I've been thinking about error handling in C++ lately. Exceptions are a great way of handling errors in most applications. However, I work in the games industry where it is commonly accepted that exceptions should be avoided, so I'm looking for a clean and simple alternative. (The validity of avoiding exceptions in games is definitely up for debate, but let's assume they shouldn't be used to remain on topic).
Errors can be considered to fall into 2 categories: expected and unexpected. An expected error is an error that is acceptable during the running of an application: no internet connection, file doesn't exist, etc. Unexpected errors are those which should never happen and are usually bugs: NaN values, null pointer issues, etc. The difference between the two is nicely explained in this blog.
In an app which doesn't make use of exception, it is typical for the app to abort when an unexpected error occurs - either through use of
I can see problems with the way expected errors are often handled however. There are two issues. First of all, it is common to see methods like this:
This is quite messy. Out-parameters make for a more untidy API (Subjective, but in my opinion this is the case). A common solution is something like boost::Optional or a Maybe monad.
This seems decent, but leads us to my second issue: we've lost all context of the error. If the optional value is null, we have no idea why. A possible solution would be to return a struct, containing the error code and the optional value:
```
enum class Error
{
k_success,
k_fileDoesNotExist,
k_badPermissions
};
struct Result
{
Error m_error;
Optional m_contents;
};
Result r
Errors can be considered to fall into 2 categories: expected and unexpected. An expected error is an error that is acceptable during the running of an application: no internet connection, file doesn't exist, etc. Unexpected errors are those which should never happen and are usually bugs: NaN values, null pointer issues, etc. The difference between the two is nicely explained in this blog.
In an app which doesn't make use of exception, it is typical for the app to abort when an unexpected error occurs - either through use of
assert() or some abort method/macro that allows an error message to be supplied: FATAL_ERROR("Null pointer!"). This seems perfectly acceptable, so I'm not concerned with this case. I can see problems with the way expected errors are often handled however. There are two issues. First of all, it is common to see methods like this:
bool readFile(std::string& out_contents); //returns false if the file couldn't be read.This is quite messy. Out-parameters make for a more untidy API (Subjective, but in my opinion this is the case). A common solution is something like boost::Optional or a Maybe monad.
Optional readFile();This seems decent, but leads us to my second issue: we've lost all context of the error. If the optional value is null, we have no idea why. A possible solution would be to return a struct, containing the error code and the optional value:
```
enum class Error
{
k_success,
k_fileDoesNotExist,
k_badPermissions
};
struct Result
{
Error m_error;
Optional m_contents;
};
Result r
Solution
I'm not familiar enough with C++ to know their standards, or what the elders consider to be the best way of dealing with errors are (and C++ is definitely a language of elders), but I can give you some insight on what another equally old object-oriented language does to handle errors (and it's not just throwing exceptions).
Enter Objective-C.
In Objective-C, errors and exceptions are considered entirely different.
And there's a pretty simple principle guiding when you should use an exception and when you should use an error.
So, keeping in mind that I'm no C++ expert, so pardon any syntax, a method to read an entire file at a particular path and return a string containing all its contents might look something like this:
Objective-C methods taking this approach will always be documented clearly to indicate that the return value indicates success or failure (if
Now, the advantage of using this approach over exceptions is that if we want, much like with error codes, we can simply ignore the error. Or perhaps, more importantly, we might simply not care about the detail of the error. With exceptions, you can ignore the detail of the exception, but you still have to catch the exception. With this approach, you just pass
The advantage of using this approach over error codes is that it's far more verbose. Error codes, as a start, require knowing to find the library's definition of all their error code constants, so you can at least get the code's name. Then, if you wanted to do something as output a simple description of each error, you have to write a function to convert the code into a human-readable string. And to even get that human-readable string, you either have to make up your own based on the error code's name, or hope the library has great documentation. And even then, it's a static description that is applied to all cases of that code.
The error object simply contains these sort of details.
The Objective-C version of the error object is called
It has three important properties.
That dictionary can contain anything it wants, but it usually will contain values for a few predefined keys:
-
-
-
(For more information on these, I recommend looking at the Apple docs regarding
Enter Objective-C.
In Objective-C, errors and exceptions are considered entirely different.
- Exceptions are thrown and can be caught, just as you'd expect.
- Errors are passed by reference into a method that expects to have an error and can be checked afterward to determine the nature of the problem.
And there's a pretty simple principle guiding when you should use an exception and when you should use an error.
- Exceptions are used for programmer mistakes that are easily prevented by writing safer code (index out of bounds, calling a method on an object that doesn't implement it, etc).
- Errors are for run-time problems that can't be entirely prevented (network problems, file I/O, etc).
So, keeping in mind that I'm no C++ expert, so pardon any syntax, a method to read an entire file at a particular path and return a string containing all its contents might look something like this:
string stringWithContentsOfFile(string filePath, Error *error);Objective-C methods taking this approach will always be documented clearly to indicate that the return value indicates success or failure (if
NULL then failure, otherwise success), and that the contents of error are only worth checking on success (because of the way many Objective-C libraries work, the error might have some sort of placeholder and be non-NULL even in the case of success).Now, the advantage of using this approach over exceptions is that if we want, much like with error codes, we can simply ignore the error. Or perhaps, more importantly, we might simply not care about the detail of the error. With exceptions, you can ignore the detail of the exception, but you still have to catch the exception. With this approach, you just pass
NULL for the second argument. Whether or not the method returned NULL is your indication of whether or not it worked.The advantage of using this approach over error codes is that it's far more verbose. Error codes, as a start, require knowing to find the library's definition of all their error code constants, so you can at least get the code's name. Then, if you wanted to do something as output a simple description of each error, you have to write a function to convert the code into a human-readable string. And to even get that human-readable string, you either have to make up your own based on the error code's name, or hope the library has great documentation. And even then, it's a static description that is applied to all cases of that code.
The error object simply contains these sort of details.
The Objective-C version of the error object is called
NSError.It has three important properties.
- An
NSInteger(it's just a type def for 32-bit/64-bit platform dependent integer) calledcode. This is your "error code".
- A
NSString(just thestringclass) calleddomain. This is used to help prevent overlap of the error codes.
- An
NSDictionary(C++ equivalent ismap) that contains more detailed information about the error.
That dictionary can contain anything it wants, but it usually will contain values for a few predefined keys:
localizedDescription
localizedRecoveryOptions
localizedRecoverySuggestion
-
localizedFailureReason-
recoveryAttempter-
helpAnchor(For more information on these, I recommend looking at the Apple docs regarding
NSError.)Code Snippets
string stringWithContentsOfFile(string filePath, Error *error);Context
StackExchange Code Review Q#100633, answer score: 4
Revisions (0)
No revisions yet.