36,59 €
A recipe-based guide to refining your C++ programming skills with the help of coding best practices, advanced programming concepts, and the latest features of C++17 and C++20
Key Features
Book Description
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.
What you will learn
Who this book is for
This book is for intermediate and expert-level C++ developers who are looking to explore the lesser known functionalities of the language to improve the efficiency of their code and the way they develop applications. Basic knowledge of object-oriented programming concepts and the Standard Template Library (STL) is assumed.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 465
Veröffentlichungsjahr: 2020
Copyright © 2020 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Commissioning Editor: Richa TripathiAcquisition Editor:Shriram ShekharContent Development Editor:Tiksha SarangSenior Editor: Storm MannTechnical Editor:Romy DiasCopy Editor: Safis EditingProject Coordinator:Francy PuthiryProofreader: Safis EditingIndexer:Rekha NairProduction Designer:Jyoti Chauhan
First published: January 2020
Production reference: 1290120
Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK.
ISBN 978-1-83855-991-5
www.packt.com
Packt.com
Subscribe to our online digital library for full access to over 7,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.
Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
Get a free eBook or video every month
Fully searchable for easy access to vital information
Copy and paste, print, and bookmark content
Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.packt.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at [email protected] for more details.
At www.packt.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.
Dr. Rian Quinn is the Chief Technology Officer (CTO) in the Advanced Technologies Business Unit at Assured Information Security, Inc., having focused on trusted computing, hypervisor-related technologies, machine learning/artificial intelligence, and cybersecurity for more than 10 years, and has 9 years of technical management and business development experience. He holds a Ph.D. in computer engineering, with specializations in information assurance and computer architectures, from Binghamton University. He is the cofounder and lead developer of the Bareflank hypervisor, and is an active member of several open source projects, including Microsoft's Guideline Support Library (GSL) and OpenXT. Rian previously wrote Hands-On System Programming with C++.
Sergey Gomon started his journey in IT 12 years ago at Belarus State University of Informatics and Radioelectronics, in the Artificial Intelligence department. He has about 8 years of industrial programming experience using C++ in several fields, such as network programming, information security, and image processing. He currently works at SolarWinds MSP and is an activist in the CoreHard C++ community.
Zhuo Qingliang (also known as KDr2 online) is presently working at paodingai.com, a start-up FinTech company in China that is dedicated to improving the financial industry by using artificial intelligence technologies. He has over 10 years of experience in Linux, C, C++, Python, Perl, and Java development. He is interested in programming, doing consulting work, and participating in and contributing to the open source community (including the Julia community, of course).
If you're interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.
Title Page
Copyright and Credits
Advanced C++ Programming Cookbook
Dedication
About Packt
Why subscribe?
Contributors
About the author
About the reviewers
Packt is searching for authors like you
Preface
Who this book is for
What this book covers
To get the most out of this book
Download the example code files
Code in Action
Conventions used
Sections
Getting ready
How to do it…
How it works…
There's more…
See also
Get in touch
Reviews
Getting Started with Library Development
Technical requirements
Understanding the principle of least surprise
Getting ready
How to do it...
How it works...
Example 1
Example 2
Example 3
Example 4
Example 5
Example 6
Example 7
Example 8
Example 9
How to namespace everything
Getting ready
How to do it...
How it works...
Example 1
Example 2
Header-only libraries
Getting ready
How to do it...
How it works...
How to handle includes
Global variables
Issues with C-style macros
How to implement a large library as header-only
Learning library development best practices
Getting ready
How to do it...
How it works... 
What about warnings?
Static and dynamic analysis
Documentation
CII Best Practices
Learning how to use the boost APIs
Getting ready
How to do it...
How it works... 
Example 1
Example 2
See also
Using Exceptions for Error Handling
Technical requirements
Using the noexcept specifier
Getting ready
How to do it...
How it works...
Using the noexcept operator
Getting ready
How to do it...
How it works...
Using RAII
Getting ready
How to do it...
How it works...
Learning why to never throw exceptions in destructors
Getting ready
How to do it...
How it works...
Easily creating your own exception classes
Getting ready
How to do it...
How it works...
Implementing Move Semantics
Technical requirements
Using compiler-generated special class member functions and the Big Five
Getting ready
How to do it...
How it works...
Making your class movable
Getting ready
How to do it...
How it works...
Move-only types
Getting ready
How to do it...
How it works...
Implementing the noexcept move constructor
Getting ready
How to do it...
How it works...
Learning to be wary of const&&
Getting ready
How to do it...
How it works...
Referencing qualified member functions
Getting ready
How to do it...
How it works...
Exploring objects that cannot be moved or copied
Getting ready
How to do it...
How it works...
Using Templates for Generic Programming
Technical requirements
Implementing SFINAE
Getting ready
How to do it...
How it works...
Learning perfect forwarding
Getting ready
How to do it...
How it works...
Using if constexpr
Getting ready
How to do it...
How it works...
Using tuples to work with parameter packs
Getting ready
How to do it...
How it works...
Using type traits to overload functions and objects
Getting ready
How to do it...
How it works...
Learning how to implement template<auto>
Getting ready
How to do it...
How it works...
Working with explicit template declarations
Getting ready
How to do it...
How it works...
Concurrency and Synchronization
Technical requirements
Working with mutexes
Getting ready
How to do it...
How it works...
std::mutex
std::lock_guard
std::recursive_mutex
std::shared_mutex
std::timed_mutex
Using atomic data types
Getting ready
How to do it...
How it works...
Understanding what const & mutable mean in the context of multiple threads
Getting ready
How to do it...
How it works...
Making a class thread-safe
Getting ready
How to do it...
How it works...
Synchronization wrappers and how to implement them
Getting ready
How to do it...
How it works...
Blocking operations versus asynchronous programming
Getting ready
How to do it...
How it works...
Working with promises and futures
Getting ready
How to do it...
How it works...
Optimizing Your Code for Performance
Technical requirements
Benchmarking your code
Getting ready
How to do it...
How it works...
Looking at assembly code
Getting ready
How to do it...
How it works...
Reducing the number of memory allocations
Getting ready
How to do it...
How it works...
Declaring noexcept
Getting ready
How to do it...
How it works...
Debugging and Testing
Technical requirements
Getting to grips with unit testing
Getting ready
How to do it...
How it works...
Working with ASAN, the address sanitizer
Getting ready
How to do it...
How it works...
Memory leak error
Memory deleted twice
Accessing invalid memory 
Using memory after deleting it
Deleting memory that was never allocated
Working with UBSAN, the undefined behavior sanitizer
Getting ready
How to do it...
How it works...
Divide-by-zero errors
Null-pointer dereferences
Out-of-bounds errors
Overflow errors
Using #ifndef NDEBUG to conditionally execute additional checks
Getting ready
How to do it...
How it works...
Creating and Implementing Your Own Container
Technical requirements
Using a simple wrapper around std::vector
Getting ready
How to do it...
How it works...
Default constructor
Custom allocator constructor
Count constructors
Copy/move constructors
Initializer list constructor
Usage
Adding elements to our container
Usage of push/emplace
Adding the relevant parts of the std::set API
Getting ready
How to do it...
How it works...
Working with iterators
Getting ready
How to do it...
How it works...
Adding the relevant parts of the std::vector API
Getting ready
How to do it...
How it works...
Exploring Type Erasure
Technical requirements
How to erase a type with inheritance
Getting ready
How to do it...
How it works...
Using C++ templates to write generic functions
Getting ready
How to do it...
How it works...
There's more...
See also
Learning the C++ type eraser pattern
Getting ready
How to do it...
How it works...
Implementing delegates with type erasing
Getting ready
How to do it...
How it works...
Adding a function signature to our delegate
Adding const support to our delegate
Adding support for one-to-many to our delegate
Adding support for non-member functions to our delegate
An In-Depth Look at Dynamic Allocation
Technical requirements
Comparing std::shared_ptr and std::unique_ptr
Getting ready
How to do it...
How it works...
Converting from a std::unique_ptr into a std::shared_ptr
Getting ready
How to do it...
How it works...
Working with circular references
Getting ready
How to do it...
How it works...
Typecasting with smart pointers
Getting ready
How to do it...
How it works...
The heap under a microscope
 Getting ready
How to do it...
How it works...
Common Patterns in C++
Technical requirements
Learning the factory pattern
Getting ready
How to do it...
How it works...
Using the singleton pattern properly
Getting ready
How to do it...
How it works...
Extending your objects with the decorator pattern
Getting ready
How to do it...
How it works...
Adding communication with the observer pattern
Getting ready
How to do it...
How it works...
Improving performance with static polymorphism
Getting ready
How to do it...
How it works...
A Closer Look at Type Deduction
Technical requirements
Using auto and type deduction
Getting ready
How to do it...
How it works...
Learning how decltype type deduction rules work
Getting ready
How to do it...
How it works...
Working with template function type deduction
Getting ready
How to do it...
How it works...
Leveraging template class type deduction in C++17
Getting ready
How to do it...
How it works...
Working with user-defined type deduction in C++17
Getting ready
How to do it...
How it works...
Bonus - Using C++20 Features
Technical requirements
Looking at Concepts in C++20
Getting ready
How to do it...
How it works...
Working with Modules in C++20
Getting ready
How to do it...
How it works...
Introducing std::span, a new view on arrays
Getting ready
How to do it...
How it works...
Working with Ranges in C++20
Getting ready
How to do it...
How it works...
Learning how to use Coroutines in C++20
Getting ready
How to do it...
How it works...
Other Books You May Enjoy
Leave a review - let other readers know what you think
In this book, you will learn advanced C++ techniques that you can use on your own C++ projects. This book teaches C++ using a recipe-style approach, complete with examples and screenshots for each recipe that you can download from GitHub and work with yourself. This book teaches C++ using the C++17 specification, with a sneak peek at the new features being added to C++20 at the end. In some recipes, we will even use a disassembler to better understand how C++ is compiled, and the impact certain decisions have on your applications. By the end of this book, you will have mastered the advanced concepts of C++ and will be able to solve everyday problems, which will take your C++ programming to the next level.
This book is for intermediate C++ developers who are familiar with C++ and want to obtain expert skills and become a proficient C++ developer. A good understanding of the language is assumed, including a basic understanding of assembly.
Chapter 1, Getting Started with Library Development, teaches you how to develop your own libraries, including an explanation of the principle of least surprise, how to namespace everything, how to write header-only libraries, and how to ensure others will continue to use your libraries.
Chapter 2, Using Exceptions for Error Handling, covers more advanced topics of C++ exception and error handling, including a detailed explanation of the noexcept specifier and operator, how RAII supports resource management in the presence of exceptions, why throwing from a destructor should be avoided, and how to write your own exceptions.
Chapter 3, Implementing Move Semantics, provides a detailed explanation of C++ move semantics, including an explanation of the Big Five, how to make your class movable, how to write move-only (and non-move) non-copy style classes, how to properly implement a move constructor, why const && makes no sense, and how to use reference qualification.
Chapter 4, Using Templates for Generic Programming, teaches you how to write template functions like an expert, including how to implement your own SFINAE, how to perform perfect forwarding, how to use constexpr-if statements, how to leverage tuples with parameter packs, how to loop over parameter packs at compile time, how to use type traits to implement different versions of the same function, how to use template<auto>, and how to leverage explicit type declarations in your own applications.
Chapter 5, Concurrency and Synchronization, teaches you how to use std::mutex (and friends), when to use atomic types, how to handleconst classes with thread-safety using the mutable keyword, how to write a thread-safe class, how to write a thread-safe wrapper, as well as how to write asynchronous C++ including promises and futures.
Chapter 6, Optimizing Your Code for Performance, covers how to profile and benchmark your C++, how to disassemble your C++ to better understand how to optimize your code, how to locate and remove unneeded memory allocations, and why noexcept helps with optimizations.
Chapter 7, Debugging and Testing, walks you through how to use Catch2 to unit test C++, how to use Google's ASAN and UBSAN sanitizers to dynamically analyze your code for memory corruption and undefined behavior, as well as how to use NDEBUG.
Chapter 8, Creating and Implementing Your Own Container, teaches you how to write your own container wrapper by creating a std::vector that is always sorted.
Chapter 9, Exploring Type Erasure, teaches you everything you need to know about type erasure, including how to erase types through inheritance and using template, how to implement the type erasure pattern, and how to implement the delegate pattern.
Chapter 10, An In-Depth Look at Dynamic Allocation, teaches you advanced topics in dynamic memory allocation, including how to properly use std::unique_ptr and std::shared_ptr, how to handle circular references, how to type cast smart pointers, and how the heap works behind the scenes to provide your application with dynamic memory.
Chapter 11, Common Patterns in C++, explains how different patterns in computer science are implemented in C++, including the factory pattern, the singleton pattern, the decorator pattern, and the observer pattern, as well as how to implement static polymorphism to write your own static interfaces without the need for virtual inheritance.
Chapter 12, A Closer Look at Type Deduction, provides a deep dive into how type deduction is performed in C++17, including howauto,decltype, and template deduce their types automatically. This chapter concludes with examples of how to write your own C++17 user-defined deduction guides.
Chapter 13, Bonus: Using C++20 Features, provides a sneak peek at the new features coming with C++20, including concepts, modules, ranges, and coroutines.
We assume that you have written C++ before and are already familiar with some modern C++ features.
This book uses Ubuntu to provide examples that you can compile and run yourself as you read the book. We assume you have some basic knowledge of Ubuntu, how to install it, and how to use a Linux terminal.
We use a disassembler in some of the recipes to better understand what the compiler is doing under the hood. Although you do not need to know how to read the assembly to understand what is being taught, a basic understanding of x86_64 assembly will help.
You can download the example code files for this book from your account at www.packt.com. If you purchased this book elsewhere, you can visit www.packtpub.com/support and register to have the files emailed directly to you.
You can download the code files by following these steps:
Log in or register at
www.packt.com
.
Select the
Support
tab.
Click on
Code Downloads
.
Enter the name of the book in the
Search
box and follow the onscreen instructions.
Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:
WinRAR/7-Zip for Windows
Zipeg/iZip/UnRarX for Mac
7-Zip/PeaZip for Linux
The code bundle for the book is also hosted on GitHub at https://github.com/PacktPublishing/Advanced-CPP-Programming-CookBook. In case there's an update to the code, it will be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
Visit the following link to check out videos of the code being run: https://bit.ly/2tQoZyW
In this book, you will find several headings that appear frequently (Getting ready, How to do it..., How it works..., There's more..., and See also).
To give clear instructions on how to complete a recipe, use these sections as follows:
This section tells you what to expect in the recipe and describes how to set up any software or any preliminary settings required for the recipe.
This section contains the steps required to follow the recipe.
This section usually consists of a detailed explanation of what happened in the previous section.
This section consists of additional information about the recipe in order to make you more knowledgeable about the recipe.
This section provides helpful links to other useful information for the recipe.
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, mention the book title in the subject of your message and email us at [email protected].
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata, selecting your book, clicking on the Errata Submission Form link, and entering the details.
Piracy: If you come across any illegal copies of our works in any form on the Internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you!
For more information about Packt, please visit packt.com.
In this chapter, we will cover some useful recipes for creating our own libraries, including an explanation of the principle of least surprise, which encourages us to implement libraries using semantics that our users are already familiar with. We will also look at how to namespace everything to ensure our custom libraries don't conflict with others. In addition, we will look at how to create header-only libraries, as well as some best practices associated with library development. Finally, we will conclude this chapter with a demonstration of the boost libraries to show you what a large library looks like and how it can be used by users in their own projects.
In this chapter, we will cover the following recipes:
Understanding the principle of least surprise
How to namespace everything
Header-only libraries
Learning library development best practices
Learning how to use the boost APIs
Let's get started!
To compile and run the examples in this chapter, you must have administrative access to a computer running Ubuntu 18.04 with a functional internet connection. Prior to running these examples, you must install the following packages using the following command:
> sudo apt-get install build-essential git cmake
If this is installed on any operating system other than Ubuntu 18.04, then GCC 7.4 or higher and CMake 3.6 or higher will be required.
When either using existing C++ libraries or creating your own, understanding the principle of least surprise (also called the principle of least astonishment) is critical to developing source code efficiently and effectively. This principle simply states that any feature that a C++ library provides should be intuitive and should operate as the developer expects. Another way of saying this is that a library's APIs should be self-documenting. Although this principle is critically important when designing libraries, it can and should be applied to all forms of software development. In this recipe, we will explore this principle in depth.
As with all of the recipes in this chapter, ensure that all of the technical requirements have been 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 you've done this, open a new Terminal. We will use this Terminal to download, compile, and run our examples.
Perform the following steps to complete this recipe:
From a new Terminal, run the following code to download the source code:
> cd ~/
> git clone https://github.com/PacktPublishing/Advanced-CPP-CookBook.git
> cd Advanced-CPP-CookBook/chapter01
To compile the source code, run the following code:
> mkdir build && cd build
> cmake ..
> make recipe01_examples
Once the source code has been compiled, you can execute each example in this recipe by running the following commands:
> ./recipe01_example01
The answer is: 42
> ./recipe01_example02
The answer is: 42
> ./recipe01_example03
The answer is: 42
> ./recipe01_example04
The answer is: 42
The answer is: 42
> ./recipe01_example05
The answer is: 42
The answer is: 42
> ./recipe01_example06
The answer is: 42
The answer is: 42
> ./recipe01_example07
The answer is: 42
> ./recipe01_example08
The answer is: 42
> ./recipe01_example09
The answer is: 42
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.
As stated in the previous section, the principle of least surprise states that a library's APIs should be intuitive and self-documenting and this principle generally applies to all forms of software development and not just library design. To understand this, we'll look at some examples.
Example 1 demonstrates the principle of least surprise as follows:
#include <iostream>int sub(int a, int b){ return a + b; }int main(void){ std::cout << "The answer is: " << sub(41, 1) << '\n'; return 0;}
As shown in the preceding example, we have implemented a library API that adds two integers and returns the results. The problem is that we named the function sub, which most developers would associate with subtraction and not addition; although the API functions as designed, it breaks the principle of least surprise because the API's name is not intuitive.
Example 3 demonstrates the principle of least surprise as follows:
#include <iostream>int add(int a, int b){ return a + b; }int main(void){ std::cout << "The answer is: " << add(41, 1) << '\n'; return 0;}
As shown in the preceding example, we're adhering to the principle of least surprise here. The API is designed to add two integers and return the result, and the API intuitively performs this action as expected.
Example 4 demonstrates the principle of least surprise as follows:
#include <stdio.h>#include <iostream>int main(void){ printf("The answer is: %d\n", 42); std::cout << "The answer is: " << 42 << '\n'; return 0;}
As shown in the preceding example, another great example of the principle of least surprise is the difference between printf() and std::cout. The printf() function requires the addition of format specifiers to output integers to stdout. There are many reasons why printf() is not intuitive:
To a beginner, the
printf()
function's name, which stands for print formatted, is not intuitive (or in other words, the function's name is not self-documenting). Other languages avoid this issue by picking more intuitive names for a print function, such as
print()
or
console()
, which do a better job of adhering to the principle of least surprise.
The format specifier symbol for an integer is
d
. Once again, to a beginner this is unintuitive. In this specific case,
d
stands for decimal, which is another way of saying
signed integer
. A better format specifier might have been
i
to match the language's use of
int
.
Contrast this with std::cout, which stands for character output. Although this is less intuitive compared to print() or console(), it is more intuitive than printf(). Furthermore, to output an integer to stdout, the user doesn't have to memorize a table of format specifiers to complete their task. Instead, they can simply use the << operator. Then, the APIs handle formatting for you, which is not only more intuitive but also safer (especially when working with std::cin as opposed to scanf()).
Example 6 demonstrates the principle of least surprise as follows:
#include <iostream>int add(int a, int b){ return a + b; }int Sub(int a, int b){ return a - b; }int main(void){ std::cout << "The answer is: " << add(41, 1) << '\n'; std::cout << "The answer is: " << Sub(43, 1) << '\n'; return 0;}
As shown in the preceding code, we have implemented two different APIs. The first adds two integers and returns the results while the second subtracts two integers and returns the results. The issue with the subtract function is two-fold:
The addition function is in lowercase while the subtraction function is in uppercase. This is not intuitive and users of the APIs would have to learn which APIs are in lowercase and which are in uppercase.
The C++ standard APIs are all in snake case, meaning they leverage lowercase words with the use of
_
to denote a space. In general, it is better to design C++ library APIs with snake case as a beginner is more likely to find this intuitive. It should be noted that, although this is generally the case, the use of snake case is highly subjective and there are several languages that do not adhere to this guidance. The most important thing is to pick a convention and stick to it.
Once again, ensuring your APIs mimic existing semantics ensures the user can quickly and easily learn to use your APIs, while reducing the probability of the user writing your APIs incorrectly, leading to compile errors.
Example 7 demonstrates the principle of least surprise as follows:
#include <queue>#include <iostream>int main(void){ std::queue<int> my_queue; my_queue.emplace(42); std::cout << "The answer is: " << my_queue.front() << '\n'; my_queue.pop(); return 0;}
As shown in the preceding example, we are showing you how a std::queue can be used to add integers to a queue, output the queue to stdout, and remove elements from the queue. The point of this example is to highlight the fact that C++ already has a standard set of naming conventions that should be leveraged during C++ library development.
If you are designing a new library, it is helpful to the user of your library to use the same naming conventions that C++ has already defined. Doing so will lower the barrier to entry and provide a more intuitive API.
Example 8 demonstrates the principle of least surprise as follows:
#include <iostream>auto add(int a, int b){ return a + b; }int main(void){ std::cout << "The answer is: " << add(41, 1) << '\n'; return 0;}
As shown in the preceding example, we are demonstrating how the use of auto, which tells the compiler to figure out what the return type of the function is automatically, does not uphold the principle of least surprise. Although auto is extremely helpful for writing generic code, its use should be avoided as much as possible when designing a library API. Specifically, for the user of the API to understand what the inputs and outputs of the API are, the user must read the API's implementation as auto does not specify the output type.
Example 9 demonstrates the principle of least surprise as follows:
#include <iostream>template <typename T>T add(T a, T b){ return a + b; }int main(void){ std::cout << "The answer is: " << add(41, 1) << '\n'; return 0;}
As shown in the preceding example, we are demonstrating a more appropriate way to uphold the principle of least surprise while simultaneously supporting generic programming. Generic programming (also called template meta-programming or programming with C++ templates) provides the programmer with a way to create an algorithm without stating the types that are being used in the algorithm. In this case, the add function doesn't dictate the input type, allowing the user to add two values of any type (in this case, the type is called T, which can take on any type that supports the add operator). Instead of returning an auto, which would not state the output type, we return a type T. Although T is not defined here as it represents any type, it does tell the user of the API that any type we input into this function will also be returned by the function. This same logic is used heavily in the C++ standard library.
When creating a library, it is important to namespace everything. Doing so ensures that of the APIs provided by the library cause name collisions with the user's code or with facilities provided by other libraries. In this recipe, we will demonstrate how to do this in our own libraries.
As with all of the recipes in this chapter, ensure that all of the technical requirements have been 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 you have done this, open a new Terminal. We will use this Terminal to download, compile, and run our examples.
You need to perform the following steps to complete this recipe:
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/chapter01
To compile the source code, run the following code:
> mkdir build && cd build
> cmake ..
> make recipe02_examples
Once the source code has been compiled, you can execute each example in this recipe by running the following commands:
> ./recipe02_example01
The answer is: 42
> ./recipe02_example02
The answer is: 42
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.
C++ provides us with the ability to wrap code in a namespace, which simply adds the namespace name to all functions and variables inside the namespace code (it should be noted that C style macros are not included in the namespace and should be used with care because C macros are a preprocessor feature that does not contribute to the code's compiled syntax). To explain why we should namespace everything when creating our own libraries, we'll look at some examples.
Example 1 demonstrates how to wrap your library's APIs in a C++ namespace :
// Contents of library.hnamespace library_name{ int my_api() { return 42; } // ...}// Contents of main.cpp#include <iostream>int main(void){ using namespace library_name; std::cout << "The answer is: " << my_api() << '\n'; return 0;}
As shown in the preceding example, the contents of the library are wrapped in a namespace and stored in the header (this example demonstrates a header-only library, which is an extremely useful design approach as the end user doesn't have to compile libraries, install them on his/her system, and then link against them). The library user simply includes the library header file and uses the using namespace library_name statement to unwrap the library's APIs. If the user has more than one library with the same API names, this statement can be omitted to remove any ambiguity.
Header-only libraries are exactly as they sound; an entire library is implemented using header files (usually a single header file). The benefit of header-only libraries is that they are easy to include in your project as you simply include the header and you are done (there is no need to compile the library as there are no source files to compile). In this recipe, we will learn about some issues that arise when attempting to create a header-only library and how to overcome them. This recipe is important because, if you plan to create your own library, a header-only library is a great place to start and will likely increase your adoption rates as downstream users will have less trouble integrating your library into their code base.
As with all of the recipes in this chapter, ensure that all of the technical requirements have been 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 you have done this, open a new Terminal. We will use this Terminal to download, compile, and run our examples.
You need to perform the following steps to complete this recipe:
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/chapter01
To compile the source code, run the following code:
> mkdir build && cd build
> cmake ..
> make recipe03_examples
Once the source code has been compiled, you can execute each example in this recipe by running the following commands:
> ./recipe03_example01
The answer is: 42
> ./recipe03_example02
The answer is: 42
> ./recipe03_example03
The answer is: 42
> ./recipe03_example04
The answer is: 42
The answer is: 2a
> ./recipe03_example05
> ./recipe03_example06
The answer is: 42
> ./recipe03_example07
The answer is: 42
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.
To create a header-only library, simply ensure that all of your code is implemented in header files, as follows:
#ifndef MY_LIBRARY#define MY_LIBRARYnamespace library_name{ int my_api() { return 42; }}#endif
The preceding example implements a simple library with a single function. The entire implementation of this library can be implemented in a single header file and included in our code as follows:
#include "my_library.h"#include <iostream>int main(void){ using namespace library_name; std::cout << "The answer is: " << my_api() << '\n'; return 0;}
Although creating header-only libraries seems simple enough, there are some issues that arise when attempting to create a header-only library that should be taken into account.
In the preceding example, you might have noticed that, when we used our custom header-only library, we included the library first. This is an essential first step to writing a header-only library. When writing examples or tests for header-only libraries, our library should be the first thing we include to ensure that all of the header's dependencies are defined in the header-only library and not in our example or test.
For example, suppose we change our library as follows:
#ifndef MY_LIBRARY#define MY_LIBRARYnamespace library_name{ void my_api() { std::cout << "The answer is: 42" << '\n'; }}#endif
As shown in the preceding code snippet, instead of returning an integer our API now outputs to stdout. We can use our new API as follows:
#include <iostream>#include "my_library.h"int main(void){ library_name::my_api(); return 0;}
Although the preceding code compiles and runs as expected, there is a bug in the code that would likely only be identified by the user of your library. Specifically, if the user of your library swaps the order of the includes or doesn't #include <iostream>, the code will fail to compile and produce the following error:
This is because the header-only library itself doesn't include all of its dependencies. Since our example put the library after other includes, our example accidentally hides this issue. For this reason, when creating your own header-only library, always include the library first in your tests and examples to ensure this type of issue never happens to your users.