disappointment 3151237 1280

C++ Core Guidelines: Rules about Exception Handling

Today’s post is about the right way to throw and catch exceptions. This means when you should throw and how you should catch an exception.

disappointment 3151237 1280

Here are the rules for today:

Let me jump directly into the first one.

E.14: Use purpose-designed user-defined types as exceptions (not built-in types)

You should not use standard exception types or even built-in types as an exception. Here are the two that don’t from the guidelines:

A built-in type

void my_code()     // Don't
{
    // ...
    throw 7;       // 7 means "moon in the 4th quarter"
    // ...
}

void your_code()   // Don't
{
    try {
        // ...
        my_code();
        // ...
    }
    catch(int i) {  // i == 7 means "input buffer too small"
        // ...
    }
}

 

In this case, the exception is just an int without any semantics. What 7 means stands in the comment, but should better be a self-describing type. The comment can be wrong. To be sure, you must look up the documentation to get an idea. You can not attach any meaningful information to an exception of kind int. If you have a 7, I assume you use at least the numbers 1 to 6 for your exception handling. 1 meaning an unspecific error, and so on. This is too sophisticated, error-prone, and hard to read and maintain.

A standard exception

void my_code()   // Don't
{
    // ...
    throw runtime_error{"moon in the 4th quarter"};
    // ...
}

void your_code()   // Don't
{
    try {
        // ...
        my_code();
        // ...
    }
    catch(const runtime_error&) {   // runtime_error means "input buffer too small"
        // ...
    }
}

 

Using a standard exception instead of a built-in type is better because you can attach additional information to an exception or build hierarchies of exceptions. This is better but not good.  Why? The exception is too generic. It’s just a runtime_error. Image the function my_code is part of an input sub-system. If the caller of the function catches the exception by std::runtime_error, he has no idea if it was a generic error, such as “input buffer too small” or a sub-system-specific error, such as “input device is not connected“.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    To overcome these issues, derive your specific exception from std::exception. Here is a short example to give you an idea:

    class InputSubSystemException: public std::exception{
        const char* what() const noexcept override {
            return "Provide more details to the exception";
        }
    };
    

    Now, the client of the input sub-system can specifically catch the exception via catch(const InputSubSystemException& ex). You can also refine the exception hierarchy by deriving from the class InputSubSystemException.

    E.15: Catch exceptions from a hierarchy by reference

    If you catch an exception from a hierarchy by value, you may become a victim of slicing.

    Imagine you derive from InputSubSystemException (rule E.14) a new exception class USBInputException and catch the exception by-value of type InputSubSystemException. Now, an exception of type USBInputException is thrown.

    void subSystem(){
        // ...
        throw USBInputException();
        // ...
    }
    
    void clientCode(){
        try{
            subSystem();
        }
        catch(InputSubSystemException e) {   // slicing may happen
            // ...
        }
    }
    

     

    By catching the USBInputException by-value to InputSubSystemException, slicing kicks in, and e has the simpler type  InputSubSystemException. Please read the details of slicing in my previous post: C++ Core Guidelines: Rules about Don’ts.

    To say it explicitly:

    1. Catch your exception by const reference and only by reference if you want to modify the exception.
    2. If you rethrow an exception e in the exception handler, use throw and not throw e. In the second case, e would be copied.

    E.16: Destructors, deallocation, and swap must never fail

    This rule is quite obvious. Destructors and deallocations should never throw because their any reliable way to handle an exception during the destruction of an object.

    swap is often used as a basic building block for implementing copy and move semantics for a type. If an exception happens during swap, you are left with a non-initialized or not fully initialized object. Read more about the noexcept swap here: C++ Core Guidelines: Comparison, Swap, and Hash.

    The following two rules for the adequate usage of try and except are pretty similar.

    E.17: Don’t try to catch every exception in every function and E.18: Minimize the use of explicit try/catch

    From the control-flow perspective, try/catch has much in common with the goto statement. This means if an exception is thrown, the control flow directly jumps to the exception handler which is maybe in a totally different function of even sub-system. In the end, you may get spaghetti code; meaning code that has difficult to predict and maintain control flow.

    In the end, we are back to rule E.1: Develop an error-handling strategy early in a design.

    Now, the question is: How should you structure your exception handling? I think you should ask yourself the question: Is it possible to handle the exception locally? If yes, do it. If not, let the exception propagate until you can sufficiently handle it. Often sub-system boundaries are the appropriate place to handle exceptions because you want to protect the client of the sub-system from arbitrary exceptions. At the boundary level, you have the regular and irregularly control flow interface. Regular communication is the functional aspect of the interface or what the system should do. Irregular communication stands for the non-functional aspects or how the system should perform. A big part of the non-functional aspects is the exception-handling and, therefore, the right place to handle the propagated exceptions.

    What’s next?

    Six rules for error handling are still left in the C++ core guidelines. They are the topic for the next post before I go on with the rules of constants and immutability.

     

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *