9

FYI: I was asked to post this on softwareengineering - this question was originally posted on stack overflow, but they are a bit funny about broad questions over there, hence I was told to post here instead...

I have searched stackoverflow for relevant questions and was surprised to find that I couldn't find any questions tagged with C++ with in depth discussions of best practices for error handling in C++.

I found this suprising for a number of reasons:

  • There are other questions which are quite famous within the community on similar subject areas. Notable examples include the in-depth discussions of how to implement the many different operators in user-defined C++ classes. (Including mathematical operators such as operator+ as well as io operators such as operator<<... I'm sure many of those reading will recall which question and answer I refer to here.)

  • There appear to be lots of possible choices to make when writing error handling code, just as there is when writing operator implementations. However, just as with operators, there is probably only one "sensible" implementation. For example, one could implement a matrix multiply using the operator<<, but this would not be the expected behaviour for this function and so it makes little logical sense to make such a decision. Similarly with errors, one can "throw" just about anything, including just std::string objects implicitly constructed from const char*'s.

Intro: C style errors, reasons not to use them in C++

I know roughly how I can implement errors and error handling code in C++, but I am not confident that I know how to do this in a way which is approved of by industry. I would like to correct my ways by asking this question.

The C style of error handling is simple: One writes functions which return int and then one defines error codes which can be returned.

There are several problems with this:

  • Error codes have to be global variables, and are probably defined in some obscure file hidden in the source code folders somewhere.
  • Error codes have to be (should be) unique. It is difficult to maintain a list of unique codes unless they are all in the same file. If one is writing an application with several independent components, it makes little to no sense to define all the error codes for two seperate components in the same file.
  • If one splits the error codes into two or more files there may be conflicts and clashes causing nonsensical code to compile ok.

Here is an example, consider a math module and a network module.

const int ERROR_CODE_MATH_DOMAIN_ERROR = 100;
const int ERROR_CODE_NETWORK_IP_UNREACHABLE = 100;

int math_function(argument)
{
    return ERROR_CODE_MATH_DOMAIN_ERROR;
}

int main()
{
    if(math_function(arguments) == ERROR_CODE_NETWORK_IP_UNREACHABLE)
    // works, and compiles, and detects the right kind of error
    // but makes literally zero sense to the reader
}

The other advantage of C++ errors using try-catch is that error flow is seperated from normal no-error program flow.

Given the above, it would be better to use try-catch symantics to handle errors.

Often when programming I am quite lazy at dealing with errors, so I often find myself defaulting to the C-style-return-an-error-code paradigm.


Question: Should I just throw a string?

I am aware that one can do this:

 void error_throwing_function()
 {
     if(true)
     {
         throw "an error has certainly occured";
     }
 }

However, some questions arrise:

  • Is this good code?
  • What kind of error does this throw? A std::string object?
  • How do I catch this kind of error without catching all errors?
  • Is this considered bad practice?

To explain the 3rd bullet in more detail, to catch this error one may do something like this:

try
{
    error_throwing_function()
}
catch (... what goes here? ...)
// catch(std::string) // does this work? does it make sense?
{
    // only option is to catch "all possible errors" - maybe?
}

Question: Is it better to implement my own classes?

Is it best practice to implement my own classes for types of error which may occur in my library? (Using inheritance.)

The C++ standard library defines a number of default errors. These can be found here.

All possible C++ error inherit from std::exception... This raises a new question linked to the previous section:

  • What happens if one throws an instance of a const char*, suc as:

    throw "a lazy error throw";

Further, the STL defines a set of default errors which are used in STL. Notable examples include the std::out_of_range error type which is thrown by the STL containers when something like at() is called and the argument references a section of memory which is out of bounds. Link

  • When implementing a new library, should one make used of the STL errors? Is it good olr bad practice to do so? Why?

  • Or should one define new classes inheriting from std::exception to implement new types of error which related to a new libary implementation?

  • Should these error types always inherit from the base class std::exception or should they sometimes inherit from another (derived) STL error type such as std::out_of_range?

Question: Any other notable best practices?

Always good to be aware of as much information as possible so is there anything I have missed so far?

This question doesn't particularly relate to any version of C++, but since most of us are writing C++11 or later code, those standards are likely of particular interest.

4
  • 5
    Searching stackoverflow for relevant questions before posting to stackoverflow is a very good practice. But this isn't stackoverflow. Please take a moment and search here. If posting here still seems wise please edit the question. It's a bit rude to make us look like your second choice. Your question needs to rise or fall here on it's own merit regardless of what someone on stackoverflow told you. Aug 11, 2021 at 19:25
  • Are you doing embedded C++ or running on a "real" computer? Mar 2 at 16:54
  • 1
    @JosephDoggie At the time of writing I was using a "real" computer with an Exception handling environment. I am aware some ES do not have support for this and therefore returning error codes from functions or using global variables to track errors are the only options. (eg: getLastError() type functions) Mar 2 at 18:01
  • Obviously, for 'real' computers you can use modern C++ paradigms; if you are working in an embedded world, it's usually safer to stay with the "C" paradigms. Of course, bosses and lead-engineers will differ, I'd do what they say in all instances, unless there is strong reason to do otherwise! Mar 2 at 20:02

2 Answers 2

13

The C++ core guidelines are the first place to look for. And there’s a whole chapter on error handling.

In short, the preferred way in modern C++ is exception handling with try catch, unless your code needs to be interoperable with C or if the “error” is not an exceptional situation but a very common one, or for a couple of other very special cases.

Now what to throw? It depends what you want to catch! Throwing strings is for short demo code. Usually, you’d throw some subclass of std::exception, preferably your own exception classes: this allows more selective catch blocs. A quick look at CEI’s secure coding standard is also very useful.

Finally, you need to be aware that a lot of posts out there on exception management and performance are outdated and rely on drawbacks of earlier exception handling in the nineties. This SO question and the selected answer document this very well.

2
  • 3
    Hmm, are C++ exceptions cost-free? In many other languages, exceptions are quite expensive if one gets thrown, and the accepted practice in those languages is to use some alternative (like error codes) if you can't afford the expense. Aug 12, 2021 at 13:51
  • 1
    @RobertHarvey a relevant remark ondeed. Exceptions in C++ are n longer what they were in the early days. See the link in the last sentence: In principle, no noticeable overhead for trying. Of course if an exception is thrown there is a slightly more important cost than a return or a good old longjmp(). After all, the code needs to find the right catch and unwind the stack to properly destroy what has to be destroyed. This is why I insisted on the fact that it should be for exceptional cases and not for a frequently expected situation (i.e.a normal result rather than a real “error”).
    – Christophe
    Aug 12, 2021 at 14:16
6

C-style errors

I wouldn't encourage using C-style error handling whenever you can do something better. But your concerns, other than type-safety, are overblown.

Let's clear up some misconceptions anyway, just in case you some day have to write error handling code in C:

  • Error codes have to be global variables

    Error codes should be constants. It's not generally useful to take the address of one, so why keep it in a variable?

    probably defined in some obscure file hidden in the source code folders somewhere.

    Error codes should be documented. If you're providing a library for other people to use, just list the error codes. There shouldn't be that many.

  • It is difficult to maintain a list of unique codes unless they are all in the same file

    C programmers got around this decades ago by using a per-library prefix. The flags used for fcntl all start F_, constants used by the pthreads library all start PTHREAD_, and constants used by your library should all share a suitable prefix too.

    If one is writing an application with several independent components, it makes little to no sense to define all the error codes for two seperate components in the same file.

    Yeah, there's no reason to do that. Just use MYLIB_C1_ and MYLIB_C2_.

Throw a string?

  • Is this good code || bad practice?

    The second. It's bad practice.

  • What kind of error does this throw?

    From the documentation:

    throw expression

    The type of the exception object is the static type of expression with top-level cv-qualifiers removed. Array and function types are adjusted to pointer and pointer to function types, respectively.

    ...

    A std::string object?

    Well, you threw a string literal so no. It'll be a const char*. You could use throw std::string{"error string"}; - but don't. It's bad practice.

    How do I catch this kind of error without catching all errors?

    Is it unreasonable to expect you to read the free documentation on how exceptions work before asking a broad question about best practices? Most of your question is about really basic stuff you need in order to understand those best practices.

    This is also documented. A catch block's type specifier works just the same way as a function argument. If you want to catch a std::string, you catch (const std::string& e), and if you want to catch a string literal, you can catch (const char* e).

Implement my own classes?

Sure, if they're going to do something different to (or need to be differentiable from) the standard library errors.

All possible C++ error inherit from std::exception

No, that's not what it says at all. Read more carefully. The page you linked says

All exceptions generated by the standard library inherit from std::exception

(my emphasis). Not "all possible" exceptions. There's no problem at all using a type that doesn't inherit from std::exception, and it never suggested there was.

should one make used of the [standard library] errors?

Sure, if they meet your needs.

should one define new classes inheriting from std::exception to implement new types of error

Sure, if the existing ones don't meet your needs.

Should these error types always inherit from the base class std::exception or should they sometimes inherit from another (derived) [standard library] error type such as std::out_of_range?

If your custom out-of-range exception has the same meaning but optionally provides more information, in a form some but not all catch handlers may want to use ... it makes sense to extend the existing out_of_range.

If your custom database-replication-consistency-error exception is completely unrelated to any of the existing runtime or logic error categories, it probably shouldn't inherit from them. It's reasonable still to inherit from std::exception if some client code may expect it to.

Exception classes have two only purposes: to give client code a way to catch them by type (where the error type affects how the client code reacts), and to pass information.

Use a new type where the client needs distinct behaviour, and/or where you want to pass it new information. This is ... basically exactly the same reasoning as reusing-or-extending any other class.

5
  • 1
    A "const global variable" is what I meant by global variable - it's the same thing. Aug 13, 2021 at 17:10
  • 1
    Obviously I did read the documentation however clearly a few things were not completely clear - hence the question. Your comments clarified some of them. Aug 13, 2021 at 17:13
  • 1
    By documentation I mean the info at en.cppreference.com btw Aug 13, 2021 at 17:16
  • 1
    A const global variable is still different to a constant. Global variables can still have their address taken even if they're const and this yields a const*. Constants cannot.
    – Useless
    Jun 24, 2022 at 13:04
  • C-style is especially useful if you are working in an embedded (or high-performance) environment. Mar 2 at 20:03

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.