The noexcept operator is used to determine whether a function can throw. Let's start with a simple example:
#include <iostream>
#include <stdexcept>
void foo()
{
std::cout << "The answer is: 42\n";
}
int main(void)
{
std::cout << std::boolalpha;
std::cout << "could foo throw: " << !noexcept(foo()) << '\n';
return 0;
}
This results in the following:
As shown in the preceding example, we defined a foo() function that outputs to stdout. We don't actually execute foo() but, instead, we use the noexcept operator to check to see whether the foo() function could throw. As you can see, the answer is yes; this function can throw. This is because we did not label the foo() function with noexcept, and, as stated in the previous recipe, functions can throw by default.
It should also be noted that we added ! to the noexcept expression. This is because noexcept returns true if the function is labeled noexcept, which means that the function is not allowed to throw. However, in our example, we are not asking whether the function cannot throw, but instead we are asking whether the function can throw, hence the logical Boolean reversal.
Let's expand upon this by adding a couple more functions to our example. Specifically, in the following example, we will add some functions that throw as well as some functions that are labeled noexcept:
#include <iostream>
#include <stdexcept>
void foo1()
{
std::cout << "The answer is: 42\n";
}
void foo2()
{
throw std::runtime_error("The answer is: 42");
}
void foo3() noexcept
{
std::cout << "The answer is: 42\n";
}
void foo4() noexcept
{
throw std::runtime_error("The answer is: 42");
}
int main(void)
{
std::cout << std::boolalpha;
std::cout << "could foo throw: " << !noexcept(foo1()) << '\n';
std::cout << "could foo throw: " << !noexcept(foo2()) << '\n';
std::cout << "could foo throw: " << !noexcept(foo3()) << '\n';
std::cout << "could foo throw: " << !noexcept(foo4()) << '\n';
return 0;
}
This results in the following:
As shown in the preceding example, if a function is labeled with noexcept, the noexcept operator returns true (which, in our example, outputs false). More importantly, a keen observer would notice that the functions that throw exceptions do not change the output of the noexcept operator. That is, the noexcept operator returns false if a function can throw an exception, not if it will throw an exception. This is important as the only way to know whether a function will throw an exception is to execute it. The only thing the noexcept specifier states is whether or not an exception is allowed to be thrown by the function. It doesn't state whether or not an exception will be thrown. By extension, the noexcept operator doesn't tell you whether the function will throw or not but instead tells you whether the function is labeled with the noexcept specifier (and, more importantly, what the noexcept specifier evaluates to).
Before we attempt to use the noexcept specifier in a more realistic example, let's look at the following example:
#include <iostream>
#include <stdexcept>
void foo()
{
throw std::runtime_error("The answer is: 42");
}
int main(void)
{
foo();
}
As shown in the preceding example, we have defined a foo() function that throws, and then we call this function from our main function, resulting in std::terminate() being called because we didn't handle the exception before leaving the program. In a more complicated setting, we might not know whether foo() throws or not, and, as a result, we may not want to add the additional overhead of exception handling if it is not needed. To better explain this, let's examine the resulting assembly code for the main() function for this example:
As you can see, the main function is simple and doesn't contain any additional logic outside of calling the foo function. Specifically, the main function doesn't have any catch logic in it.
Now, let's use the noexcept operator in a more concrete example:
#include <iostream>
#include <stdexcept>
void foo()
{
throw std::runtime_error("The answer is: 42");
}
int main(void)
{
if constexpr(noexcept(foo())) {
foo();
}
else {
try {
foo();
}
catch (...)
{ }
}
}
As shown in the preceding example, we use the noexcept operator in conjunction with the constepxr operator in the if statement that was added in C++17. This allows us to ask the compiler whether foo() is allowed to throw. If it is, we execute the foo() function inside a try/catch block so that we can handle any possible exceptions as needed. If we examine the assembly of this function, as shown in the following screenshot, we can see that some additional catch logic was added to the resulting binary to handle the exceptions as needed:
Now, let's take this same example one step further by stating that the foo() function is not allowed to throw using the noexcept specifier:
#include <iostream>
#include <stdexcept>
void foo() noexcept
{
throw std::runtime_error("The answer is: 42");
}
int main(void)
{
if constexpr(noexcept(foo())) {
foo();
}
else {
try {
foo();
}
catch (...)
{ }
}
}
As shown in the preceding example, the program calls std::terminate() since the foo() function was labeled noexcept. Furthermore, if we look at the resulting assembly, we can see that the main() function no longer contains the additional try/catch logic, which means that our optimization worked:
Finally, we might possibly not know how to label our own function if we do not know whether a function that was called can throw or not. Let's look at the following example to demonstrate this issue:
#include <iostream>
#include <stdexcept>
void foo1()
{
std::cout << "The answer is: 42\n";
}
void foo2() noexcept(noexcept(foo1()))
{
foo1();
}
int main(void)
{
std::cout << std::boolalpha;
std::cout << "could foo throw: " << !noexcept(foo1()) << '\n';
std::cout << "could foo throw: " << !noexcept(foo2()) << '\n';
}
This results in the following:
As shown in the preceding example, the foo1() function is not labeled with the noexcept specifier, which means it is allowed to throw an exception. In foo2(), we want to ensure that our noexcept specifier is correct but we call foo1(), and, in this example, we assume that we don't know whether foo1() is noexcept or not.
To ensure foo2() is labeled properly, we combine the lessons learned in this recipe and the previous one to mark the function properly. Specifically, we use the noexcept operator to tell us whether the foo1() function will throw, and then we use the noexcept specifier's Boolean expression syntax to use the results of the noexcept operator to label foo2() as noexcept or not. If foo1() is labeled with noexcept, the noexcept operator will return true, resulting in foo2() being marked as noexcept(true), which is the same as simply stating noexcept. If foo1() is not labeled as noexcept, the noexcept operator will return false, in which case the noexcept specifier will be labeled as noexcept(false), which is the same as not adding the noexcept specifier (that is, the function is allowed to throw an exception).