Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Advanced C++ Programming Cookbook

You're reading from   Advanced C++ Programming Cookbook Become an expert C++ programmer by mastering concepts like templates, concurrency, and type deduction

Arrow left icon
Product type Paperback
Published in Jan 2020
Publisher Packt
ISBN-13 9781838559915
Length 454 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Dr. Rian Quinn Dr. Rian Quinn
Author Profile Icon Dr. Rian Quinn
Dr. Rian Quinn
Arrow right icon
View More author details
Toc

Table of Contents (15) Chapters Close

Preface 1. Getting Started with Library Development 2. Using Exceptions for Error Handling FREE CHAPTER 3. Implementing Move Semantics 4. Using Templates for Generic Programming 5. Concurrency and Synchronization 6. Optimizing Your Code for Performance 7. Debugging and Testing 8. Creating and Implementing Your Own Container 9. Exploring Type Erasure 10. An In-Depth Look at Dynamic Allocation 11. Common Patterns in C++ 12. A Closer Look at Type Deduction 13. Bonus - Using C++20 Features 14. Other Books You May Enjoy

Using the noexcept operator

The noexcept operator is a compile-time check that is used to ask the compiler whether a function is labeled noexcept or not. With C++17, this can be paired with a compile-time if statement (that is, an if statement that is evaluated at compile time and that can be used to add/remove code from an executable during compilation) to change the semantics of a program based on whether or not a function is allowed to throw an exception.

In this recipe, we will explore how to use the noexcept operator in your own code. This operator is important because, in some cases, you may not know whether a function is capable of throwing an exception by simply looking at its definition. For example, if a function uses the noexcept specifier, your code might not be able to determine whether the function will throw, as you might not know—based on the function's inputs—what the noexcept specifier will evaluate to. The noexcept operator provides you with a mechanism to handle these types of scenarios, which is essential, especially when metaprogramming.

Getting ready

Before beginning, please ensure that all of the technical requirements are met, including installing Ubuntu 18.04 or higher and running the following in a Terminal window:

> sudo apt-get install build-essential git cmake

This will ensure your operating system has the proper tools to compile and execute the examples in this recipe. Once this is complete, open a new Terminal. We will use this Terminal to download, compile, and run our examples.

How to do it...

Perform the following steps to try the recipe:

  1. From a new Terminal, run the following to download the source code:
> cd ~/
> git clone https://github.com/PacktPublishing/Advanced-CPP-CookBook.git
> cd Advanced-CPP-CookBook/chapter02
  1. To compile the source code, run the following:
> mkdir build && cd build
> cmake ..
> make recipe02_examples

  1. Once the source code is compiled, you can execute each example in this recipe by running the following commands:
> ./recipe02_example01
could foo throw: true

> ./recipe02_example02
could foo throw: true
could foo throw: true
could foo throw: false
could foo throw: false

> ./recipe02_example03
terminate called after throwing an instance of 'std::runtime_error'
what(): The answer is: 42
Aborted

> ./recipe02_example04

> ./recipe02_example05
terminate called after throwing an instance of 'std::runtime_error'
what(): The answer is: 42
Aborted

> ./recipe02_example06
could foo throw: true
could foo throw: true

In the next section, we will step through each of these examples and explain what each example program does and how it relates to the lessons being taught in this recipe.

How it works...

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).

You have been reading a chapter from
Advanced C++ Programming Cookbook
Published in: Jan 2020
Publisher: Packt
ISBN-13: 9781838559915
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image