Book Image

Advanced C++ Programming Cookbook

By : Dr. Rian Quinn
Book Image

Advanced C++ Programming Cookbook

By: Dr. Rian Quinn

Overview of this book

If you think you've mastered C++ and know everything it takes to write robust applications, you'll be in for a surprise. With this book, you'll gain comprehensive insights into C++, covering exclusive tips and interesting techniques to enhance your app development process. You'll kick off with the basic principles of library design and development, which will help you understand how to write reusable and maintainable code. You'll then discover the importance of exception safety, and how you can avoid unexpected errors or bugs in your code. The book will take you through the modern elements of C++, such as move semantics, type deductions, and coroutines. As you advance, you'll delve into template programming - the standard tool for most library developers looking to achieve high code reusability. You'll explore the STL and learn how to avoid common pitfalls while implementing templates. Later, you'll learn about the problems of multithreaded programming such as data races, deadlocks, and thread starvation. You'll also learn high-performance programming by using benchmarking tools and libraries. Finally, you'll discover advanced techniques for debugging and testing to ensure code reliability. By the end of this book, you'll have become an expert at C++ programming and will have gained the skills to solve complex development problems with ease.
Table of Contents (15 chapters)

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