teddy 562960 1280

C++ Core Guidelines: The noexcept Specifier and Operator

If you skim the remaining rules to error handling, you often read the word noexcept. Before I write about the rules for error handling, I will write about the noexcept specifier and the noexcept operator in this post. 

teddy 562960 1280

noexcept

noexcept exists in two forms since C++11: as a specifier and as an operator. The C++ core guidelines use the specifier.

noexcept as specifier

By declaring a function, a method, or a lambda function as noexcept, you specify that these do not throw an exception, and if they throw, you do not care and let the program crash. For simplicity reasons, I will write about function but also mean methods and function templates. There are various ways to express your intention:

void func1() noexcept;        // does not throw
void func2() noexcept(true);  // does not throw
void func3() throw();         // does not throw

void func4() noexcept(false); // may throw

 

The noexcept specification is equivalent to the noexcept(true) specification. throw() is equivalent to noexcept(true) but was deprecated with C++11 and will be removed with C++20. In contrast, noexcept(false) means that the function may throw an exception. The noexcept specification is part of the function type but can not be used for function overloading. 

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    There are two good reasons for using noexcept: First, an exception specifier documents the function’s behavior. If a function is specified as noexcept, it can be safely used in a non-throwing function. Second, it is an optimization opportunity for the compiler. noexcept may not call std::unexpectedand may not unwind the stack. The initialization of a container may cheaply move the elements into the container if the move constructor is declared as noexcept. If not declared as noexcept, the elements may be expensive copied into the container.

    Each function in C++ is either non-throwing or potentially throwing. Potentially throwing means:

    1. The function may use a function that may throw.
    2. The function is declared without a noexcept specification.
    3. The function uses a dynamic_cast to a reference type.

    There is an exception to rule 2, that functions are potentially throwing if they have no noexcept specification. These exceptions include the following six special member functions. They are implicitly non-throwing.

    • Default constructor and destructor
    • Move and copy constructor
    • Move and copy assignment operator

    This special six member such as the destructor, can only be non-throwing if all destructors of the attributes and the bases-classes are non-throwing. Of course, the corresponding statement will hold for the five other special member functions.

    What happens when you throw an exception in a function declared non-throwing? In this case, std::terminate is called. std::terminate calls the currently installed std::terminate_handler which calls std::abort by default.The result is an abnormal program termination.

    For completeness, is want to present noexcept as operator.

    noexcept as operator

    The noexcept operator checks at compile-time if an expression does not throw an exception. The noexcept operator does not evaluate the expression. It can be used in a noexcept specifier of a function template to declare that the function may throw exceptions depending on the current type.

    To make my description clear, here is a simple example of a function template that copies its return value.

     

    // noexceptOperator.cpp
    
    #include <iostream>
    #include <array>
    #include <vector>
    
    class NoexceptCopy{
    public:
      std::array<int, 5> arr{1, 2, 3, 4, 5};             // (2)
    };
    
    class NonNoexceptCopy{
    public:
      std::vector<int> v{1, 2, 3, 4 , 5};                // (3)
    };
    
    template <typename T> 
    T copy(T const& src) noexcept(noexcept(T(src))){     // (1)
      return src; 
    }
    
    int main(){
        
        NoexceptCopy noexceptCopy;
        NonNoexceptCopy nonNoexceptCopy;
        
        std::cout << std::boolalpha << std::endl;
        
        std::cout << "noexcept(copy(noexceptCopy)): " <<            // (4)
                      noexcept(copy(noexceptCopy)) << std::endl;
                       
        std::cout << "noexcept(copy(nonNoexceptCopy)): " <<         // (5)
                      noexcept(copy(nonNoexceptCopy)) << std::endl;
    
        std::cout << std::endl;
    
    }
    

     

    Of course, the most exciting line in this example is line (1). In particular, the expression noexcept(noexcept(T(src)). The inner noexcept ist the noexcept operator, and the outer is the noexcept specifier. The expression noexcept(T(src)) checks if the copy constructor is non-throwing.This is the case for the class Noexcept (2) but not for the class NonNoexcept (3) because of the copy constructor of std::vector that may throw. Consequently, expression (4) returns true, and expression (5) returns false.

    noexceptOperator

    Maybe you know about it. You can check at compile time with the help of the type traits library if a type T has a non-throwing copy constructor: std::is_nothrow_copy_constructible::value. Based on this predicate, you can use instead of the noexcept operator the predicate from the type traits library:

     

    template <typename T> 
    T copy(T const& src) noexcept(std::is_nothrow_copy_constructible<T>::value){
      return src; 
    }
    

     

    I don’t know which version of copy do you prefer. I prefer the type traits version because it is more expressive.

    The following rule is about the noexcept specifier.

    E.12: Use noexcept when exiting a function because of a throw is impossible or unacceptable

    The title of this rule may be a little bit confusing. It says that you should declare a function as noexcept, if

    • it does not throw or
    • you don’t care in case of an exception. You are willing to crash the program because you can not handle an exception such as std::bad_alloc due to memory exhaustion.

     It’s not a good idea to throw an exception if you are the direct owner of an object.

    E.13: Never throw while being the direct owner of an object

    Here is an example to direct ownership from the guidelines:

    void leak(int x)   // don't: may leak
    {
        auto p = new int{7};
        if (x < 0) throw Get_me_out_of_here{};  // may leak *p
        // ...
        delete p;   // we may never get here
    }
    

     

    If the throw is fired, the memory is lost, and you leak. The simple solution is to remove the ownership and make the C++ runtime to the direct owner of the object. Just create a local object or at least a guard as a local object. And you know the C++ runtime takes care of local objects. Here are three variations of this idea.

    void leak(int x)   // don't: may leak
    {
        auto p1 = int{7};
        auto p2 = std::make_unique<int>(7);
        auto p3 = std::vector<int>(7);
        if (x < 0) throw Get_me_out_of_here{}; 
        // ...
    }
    

     

    p1 is locally created, but p2 and p3 are kinds of guards for the objects. The std::vector uses the heap to manage its data. Additionally, with all three variations, you eliminate the delete call.

    What’s next?

    Of course, my story, with exceptions and error handling, continues in the next post.

     

     

     

     

    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, Kris Kafka, 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, Dmitry Farberov, 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, moon, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

    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

    Seminars

    I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

    Standard Seminars (English/German)

    Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

    • C++ – The Core Language
    • C++ – The Standard Library
    • C++ – Compact
    • C++11 and C++14
    • Concurrency with Modern C++
    • Design Pattern and Architectural Pattern with C++
    • Embedded Programming with Modern C++
    • Generic Programming (Templates) with C++
    • Clean Code with Modern C++
    • C++20

    Online Seminars (German)

    Contact Me

    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 *