13

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, 2023 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, 2023 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, 2023 at 20:02

3 Answers 3

15

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.

4
  • 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
  • 1
    I think throwing / catching an exception has some cost, but a lot of it is cost that you would have to suffer anyway. For example, if exception throwing calls destructors of intermediate objects, these destructors would or should have been called with C style error codes as well.
    – gnasher729
    Nov 12, 2023 at 22:49
  • @gnasher729 Thanks for the very interesting argument. So not only has the try no overhead, but a part of the overhead of the throw could in fact not be a real overhead, as it corresponds to the destruction of objects that should anyhow be deleted. The overhead seems mostly related to a loss of potential optimisations in some cases as well as cash misses (the last link is to an SO question that goes quite deep on these elements) ;-)
    – Christophe
    Nov 12, 2023 at 23:02
9

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?

    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, 2023 at 20:03
1

It has been a number of years since I first posted this question, and I can now post my own answer to it, taking into account what I recall about why I asked this question when I did so.

Broadly speaking there are two approaches I use, if I am writing something from scratch:

  • Define a "GenericException" class, derived from std::exception, if you cannot be bothered to do something more specific. This is useful if you are doing some testing, or just sketching something out and exception handling is not your priority becuase you are not writing production code and you do not want to waste time designing and implementing a more structured exception hierachy
  • Design and implement your own hierachy of exception classes, with the base class being std::exception. You should define a seperate hierachy of exceptions for each "conceptual module" or library which you are writing. For example, if you are writing a library for performing some scientific calculations, keep those exception types independent from your library which handles IO to your server farm. (For example.)

Notes:

  • Just because std::exception is part of the standard library does not mean it is std specific, or should not be used by your own components. It is intended to be a general base class for all exception types. It isn't part of the C++ language, because no exception type is part of the C++ language, you must define your own exception types to use.
  • std::exception is defined in exception.h
  • The types in stdexcept.h are std specific and you should not use them as base classes for your own exception hierachy design

Note that in a commercial environment, most (if not all) software houses will have developed their own patterns for error handling and exception implementation, so one doesn't typically have to think very much about it.

  • I have worked in places before where everything was done with macros, and the macros handled large amounts of boilerplate for exception creation and throwing/catching
  • I don't necessarily think this is a good thing, and most people there agreed with me. Macros are considered less favorable these days, they have somewhat fallen out of favor in the C++ world because of their tendancy to produce difficult to understand, debug and maintain code
  • Elsewhere I worked on a greenfield project where we planned how exception handling was going to be done and then never really thought about it much again, because it "just worked" for our purposes

Now, let's summarize the points I asked about:

C style errors (return integer values)

  • Don't use them
  • We have exceptions to separate out "error" control flow from "regular" program flow
  • Using integer return values to signal errors is bad because it mixes regular program control flow with error signalling information
  • Further it is arduous to implement, because you have to track error codes and use these to lookup other information, for example error messages to be returned to the user as a string
  • Exceptions also allow arbitrary data to be aggregated together as part of an exception type, so leverage this advantage where necessary

Throwing a string

  • Don't do this
  • I suggested it because I wanted a quick way to signal something has gone wrong and to provide a string to the user to explain what the issue was
  • Rather than doing this, implement a GenericException which contains a std::string
  • This allows us to isolate and track errors coming from our code rather than errors coming from other libraries
// For example, something like this should be ok
class GenericException : public std::exception {

    std::string what;

public:

    GenericException(std::string what) : what(what) {}

    // implement what() here
}

To catch this specific type, do this:

try { ... }

catch(GenericException &generic_exception) {
    // GenericException specific error handling code
    // This type was thrown by our library which we are
    // currently experimenting with. It wasn't an issue
    // from the `std` library, or anyone elses library
}
catch(...) /* this literally means everything else */ { 

}

Is it better to implement my own exception hierachy

  • I covered this at the beginning
  • Mostly yes but use a Generic Exception implementation if too lazy / too inconvenient to take this approach

Other notable best practices

  • None than I can think of
  • Possibly suggest prototyping something in another language which is faster to write code in, maybe Python or Rust
  • But note that some Rust libraries use quite bad practices for error handling and there is no agreed concenus on whether using dynamic dispatch vs enum is preferable (yet)
  • But other than that Rust is really fast to write code in, very similar to C++ in many ways (it is a systems programming language) and generally less error-prone, although this doesn't necessarily reduce the number of exceptions you have to handle or design

Your Answer

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

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