36,59 €
Become an expert at C++ by learning all the key C++ concepts and working through interesting exercises
Key Features
Book Description
C++ is one of the most widely used programming languages and is applied in a variety of domains, right from gaming to graphical user interface (GUI) programming and even operating systems. If you're looking to expand your career opportunities, mastering the advanced features of C++ is key.
The book begins with advanced C++ concepts by helping you decipher the sophisticated C++ type system and understand how various stages of compilation convert source code to object code. You'll then learn how to recognize the tools that need to be used in order to control the flow of execution, capture data, and pass data around. By creating small models, you'll even discover how to use advanced lambdas and captures and express common API design patterns in C++. As you cover later chapters, you'll explore ways to optimize your code by learning about memory alignment, cache access, and the time a program takes to run. The concluding chapter will help you to maximize performance by understanding modern CPU branch prediction and how to make your code cache-friendly.
By the end of this book, you'll have developed programming skills that will set you apart from other C++ programmers.
What you will learn
Who this book is for
If you have worked in C++ but want to learn how to make the most of this language, especially for large projects, this book is for you. A general understanding of programming and knowledge of using an editor to produce code files in project directories is a must. Some experience with strongly typed languages, such as C and C++, is also recommended.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 747
Veröffentlichungsjahr: 2019
Master the technique of confidently writing robust C++ code
Gazihan Alankus
Olena Lizina
Rakesh Mane
Vivek Nagarajan
Brian Price
Copyright © 2019 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 authors, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be 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.
Authors: Gazihan Alankus, Olena Lizina, Rakesh Mane, Vivek Nagarajan, and Brian Price
Technical Reviewers: Anil Achary and Deepak Selvakumar
Managing Editor: Bhavesh Bangera
Acquisitions Editors: Kunal Sawant, Koushik Sen, and Sneha Shinde
Production Editor: Samita Warang
Editorial Board: Shubhopriya Banerjee, Bharat Botle, Ewan Buckingham, Mahesh Dhyani, Manasa Kumar, Alex Mazonowicz, Bridget Neale, Dominic Pereira, Shiny Poojary, Abhisekh Rane, Erol Staveley, Ankita Thakur, Nitesh Thakur, and Jonathan Wray.
First Published: October 2019
Production Reference: 1311019
ISBN: 978-1-83882-113-5
Published by Packt Publishing Ltd.
Livery Place, 35 Livery Street
Birmingham B3 2PB, UK
This section briefly introduces the authors, the coverage of this book, the technical skills you'll need to get started, and the hardware and software requirements required to complete all of the included activities and exercises.
C++ is one of the most widely used programming languages and is applied in a variety of domains, right from gaming to graphical user interface (GUI) programming and even operating systems. If you're looking to expand your career opportunities, mastering the advanced features of C++ is key.
The book begins with advanced C++ concepts by helping you decipher the sophisticated C++ type system and understand how various stages of compilation convert source code to object code. You'll then learn how to recognize the tools that need to be used in order to control the flow of execution, capture data, and pass data around. By creating small models, you'll even discover how to use advanced lambdas and captures and express common API design patterns in C++. As you cover later chapters, you'll explore ways to optimize your code by learning about memory alignment, cache access, and the time a program takes to run. The concluding chapter will help you to maximize performance by understanding modern CPU branch prediction and how to make your code cache-friendly.
By the end of this book, you'll have developed programming skills that will set you apart from other C++ programmers.
Gazihan Alankus holds a PhD in computer science from Washington University in St. Louis. Currently, he is an assistant professor at the Izmir University of Economics in Turkey. He teaches and conducts research on game development, mobile application development, and human-computer interaction. He is a Google developer expert in Dart and develops Flutter applications with his students in his company Gbot, which he founded in 2019.
Olena Lizina is a software developer with 5 years experience in C++. She has practical knowledge of developing systems for monitoring and managing remote computers with a lot of users for an international product company. For the last 4 years, she has been working for international outsourcing companies on automotive projects for well-known automotive concerns. She has been participating in the development of complex and highly performant applications on different projects, such as HMI (Human Machine Interface), navigation, and applications for work with sensors.
Rakesh Mane has over 18 years experience in the software industry. He has worked with proficient programmers from a variety of regions: India, the US, and Singapore. He has mostly worked in C++, Python, shell scripting, and database. In his spare time, he likes to listen to music and travel. Also, he likes to play with, experiment with, and break things using software tools and code.
Vivek Nagarajan is a self-taught programmer who started out in the 1980s on 8-bit systems. He has worked on a large number of software projects and has 14 years of professional experience with C++. Aside from this, he has worked on a wide variety of languages and frameworks across the years. He is an amateur powerlifter, DIY enthusiast, and motorcycle racer. He currently works as an independent software consultant.
Brian Price has over 30 years experience working in a variety of languages, projects, and industries, including over 20 years experience in C++. He was worked on power station simulators, SCADA systems, and medical devices. He is currently crafting software in C++, CMake, and Python for a next-generation medical device. He enjoys solving puzzles and the Euler project in a variety of languages.
By the end of this book, you will be able to:
Delve into the anatomy and workflow of C++Study the pros and cons of different approaches to coding in C++Test, run, and debug your programsLink object files as a dynamic libraryUse templates, SFINAE, constexpr if expressions and variadic templatesApply best practice to resource managementIf you have worked in C++ but want to learn how to make the most of this language, especially for large projects, this book is for you. A general understanding of programming and knowledge of using an editor to produce code files in project directories is a must. Some experience with strongly typed languages, such as C and C++, is also recommended.
This fast-paced book is designed to teach you concepts quickly through descriptive graphics and challenging exercises. The book will have "call-outs," with key take away points and the most common pitfalls to keep you interested, while breaking up the topics into manageable sections.
For the optimal student experience, we recommend the following hardware configuration:
Any entry-level PC/Mac with Windows, Linux, or macOS will sufficeProcessor: Dual core or equivalentMemory: 4 GB RAM (8 GB preferred)Storage: 35 GB of available spaceYou'll also need the following software installed in advance:
Operating system: Windows 7 SP1 32/64-bit, Windows 8.1 32/64-bit, or Windows 10 32/64-bit, Ubuntu 14.04 or later, or macOS Sierra or laterBrowser: Google Chrome or Mozilla FirefoxBefore you embark on this book, you will need to install the following libraries used in this book. You will find the steps to install these here.
Installing CMake
We will use CMake version 3.12.1 or later. We have two options for installation.
Option 1:
If you are using Ubuntu 18.10, you can install CMake globally using the following command:
sudo apt install cmake
When you run the following command:
cmake –version
You should see the following output:
cmake version 3.12.1
CMake suite maintained and supported by Kitware (kitware.com/cmake).
If the version you see here is lower than 3.12.1 (for example, 3.10), you should install CMake locally using the following instructions.
Option 2:
If you are using an older Linux version, you may get a CMake version that is lower than 3.12.1. Then, you need to install it locally. Use the following commands:
wget \
https://github.com/Kitware/CMake/releases/download/v3.15.1/cmake-3.15.1-Linux-x86_64.sh
sh cmake-3.15.1-Linux-x86_64.sh
When you see the software license, type y and press Enter. When asked about the installation location, type y and press Enter again. This should install it to a new folder in your system.
Now, we will add that folder to our path. Type the following. Note that the first line is a bit too long and the line breaks in this document. You should write it as a single line, as follows:
echo "export PATH=\"$HOME/cmake-3.15.1-Linux-x86_64/bin:$PATH\"" >> .bash_profile
source .profile
Now, when you type the following:
cmake –version
You should see the following output:
cmake version 3.15.1
CMake suite maintained and supported by Kitware (kitware.com/cmake).
3.15.1 is the current latest release at the time of writing this document. Since it is newer than 3.12.1, this will suffice for our purposes.
Installing Git
Test the current installation by typing the following:
git --version
You should see a line such as the following:
git version 2.17.1
If you see the following line instead, you need to install git:
command 'git' not found
Here is how you can install git in Ubuntu:
sudo apt install git
Installing g++
Test the current installation by typing the following:
g++ --version
You should see an output such as the following:
g++ (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
If it is not installed, type the following code to install it:
sudo apt install g++
Installing Ninja
Test the current installation by typing the following:
ninja --version
You should see an output such as the following:
1.8.2
If it is not installed, type the following code to install it:
sudo apt install ninja-build
Installing Eclipse CDT and cmake4eclipse
There are multiple ways of installing Eclipse CDT. To get the latest stable version, we will use the official installer. Go to this website and download the Linux installer: https://www.eclipse.org/downloads/packages/installer.
Follow the instructions there and install Eclipse IDE for C/C++ Developers. Once you have installed it, run the Eclipse executable. If you did not change the default configuration, typing the following command in the terminal will run it:
~/eclipse/cpp-2019-03/eclipse/eclipse
You will choose a workspace folder and then you will be greeted with a Welcome tab in the main Eclipse window.
Now, we will install cmake4eclipse. An easy way to do this is to go to this website and drag the Install icon to the Eclipse window: https://github.com/15knots/cmake4eclipse#installation. It will ask you to restart Eclipse, after which you are ready to modify the CMake project to work with Eclipse.
Installing GoogleTest
We will install GoogleTest in our system, which will also install other packages that are dependent on it. Write the following command:
sudo apt install libgtest-dev google-mock
This command installs the include files and source files for GoogleTest. Now, we need to build the source files that we installed to create the GoogleTest library. Run the following commands to do this:
cd /usr/src/gtest
sudo cmake CMakeLists.txt
sudo make
sudo cp *.a /usr/lib
Copy the code bundle for the class to the C:/Code folder.
The code bundle for this book is also hosted on GitHub at https://github.com/TrainingByPackt/Advanced-CPlusPlus.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
By the end of this chapter, you will be able to:
Establish the code-build-test processDescribe the various stages of compilationDecipher complicated C++ type systemsConfigure projects with unit testsConvert source code to object codeWrite readable code and debug itIn this chapter, we will learn to establish the code-build-test model that will be used throughout the book, write beautiful code, and perform unit tests.
C++ is one of the oldest and most popular languages that you can use to write efficient code. It is both "close to the metal," like C, and has advanced object-oriented features, like Java. Being an efficient low-level language makes C++ the language of choice for domains in which efficiency is paramount, such as games, simulations, and embedded systems. At the same time, being an object-oriented language with advanced features such as generics, references, and countless others makes it suitable for large projects that are developed and maintained by multiple people.
Almost any programming experience involves organizing your code base and using libraries written by others. C++ is no different. Unless your program is simple, you will distribute your code into multiple files that you need to organize, and you will use various libraries that fulfill tasks, usually in a much more efficient and robust way than your code would. C++ projects that do not use any third-party libraries are edge cases that do not represent the majority of projects, which use many libraries. These projects and their libraries are expected to work in different hardware architectures and operating systems. Therefore, it is important to spend time on project setup and understand the tools used to manage dependencies if you are going to develop anything meaningful with C++.
Most modern and popular high-level languages have standard tools to maintain projects, build them, and handle their library dependencies. Many of these have repositories that host libraries and tools that automatically download and use libraries from those repositories. For example, Python has pip, which takes care of downloading and using appropriate versions of libraries that the programmer wants to use. Similarly, JavaScript has npm, Java has maven, Dart has pub, and C# has NuGet. In most of these languages, you list the name of the library and the version that you would like to use, and the tool automatically downloads and uses the compatible version of the library. These languages benefit from the fact that the programs are built and run in a controlled environment in which a certain level of hardware and software requirements are satisfied. C++, on the other hand, is expected to work in a variety of contexts with different architectures, including very primitive hardware. Hence, C++ programmers are less pampered when it comes to building programs and performing dependency management.
In the world of C++, we have several tools that help in managing project sources and their dependencies. For example, pkg-config, Autotools, make, and CMake are the most notable ones in the community. Compared to the tools of the other high-level languages, these are much more complicated to use. CMake has arisen among these as the de facto standard for managing C++ projects and their dependencies. It is more opinionated compared to make, and it is accepted as the direct project format for most IDEs (Integrated Development Environments).
While CMake helps with managing projects and their dependencies, the experience is still far from higher-level languages in which you list the libraries and their versions that you want to use and everything else is taken care of for you. With CMake, you still are responsible for installing libraries properly in your development environment, and you are expected to use compatible versions for each library. In popular Linux distributions with extensive package managers, you can easily install binary versions of most popular libraries. However, sometimes, you may have to compile and install the libraries yourself. This is a part of the whole C++ developer experience, which you will gather by learning more about the development platform of your choice. Here, we will focus more on how to properly set up our CMake projects, including understanding and resolving issues related to libraries.
In order to base our discussion on a solid foundation, we will immediately start with a practical example. We will start with a C++ code base template that you can use as a starting point for your own projects. We will see how we can build and compile it using CMake on the command line. We will also set up the Eclipse IDE for C/C++ developers and import our CMake project. The use of an IDE will provide us with facilities that ease the creation of source code and enable us to debug our programs line by line to view what exactly happens during the execution of our program and correct our mistakes in an informed fashion rather than trial and error and superstition.
The de facto standard for C++ projects is to use CMake to organize and build the project. Here, we will use a basic template project as a starting point. The following is the folder structure of a sample template:
In the preceding figure, the .gitignore file lists the file patterns that should not be added to the git version control system. Such ignored files include the outputs of the build process, which are created locally and should not be shared among computers.
The files in the include and src folders are the actual C++ source files, and the CMakeLists.txt file is the CMake script file that glues the project together by handling the source compilation rules, library dependencies, and other project settings. CMake rules are high-level platform-independent rules. CMake uses them to create various types of make files for different platforms.
Building a project with CMake is a two-step process. First, we get CMake to generate platform-dependent configuration files for a native build system that will compile and build the project. Then, we will use the generated files to build the project. The platform-dependent build systems that CMake can generate configuration files for include UNIXMakefiles, Ninjabuild files, NMakeMakefiles, and MinGWMakefiles. The choice here depends on the platform in use, the availability of these tools, and personal preference. UNIXMakefiles are a de facto standard for Unix and Linux, whereas NMake is its Windows and Visual Studio counterpart. MinGW, on the other hand, is a Unix-like environment in Windows in which Makefiles are also in use. Ninja is a modern build system that provides exceptional speed compared to other build systems coupled with multi-platform support, which we choose to use here. Furthermore, in addition to these command-line build systems, we can also generate IDE projects for Visual Studio, XCode, Eclipse CDT, and many others, and build our projects inside the IDE. Therefore, CMake is a meta tool that will create the configuration files for another system that will actually build the project. In the next section, we will solve an exercise, wherein we will generate Ninjabuild files using CMake.
In this exercise, we will use CMake to generate Ninja build files, which are used to build C++ projects. We will first download our source code from a git repository and will use CMake and Ninja to build it. The aim of this exercise is to use CMake to generate Ninja build files, build the project, and then run them.
The link to the GitHub repository can be found here: https://github.com/TrainingByPackt/Advanced-CPlusPlus/tree/master/Lesson1/Exercise01/project.
Perform the following steps to complete the exercise:
In a terminal window, type the following command to download the CxxTemplate repository from GitHub onto your local system:git clone https://github.com/TrainingByPackt/Advanced-CPlusPlus/tree/master/Lesson1/Exercise01/project
The output of the previous command is similar to the following:
Now you have the source code in the CxxTemplate folder.
Navigate into the CxxTemplate folder by typing the following command in the terminal:cd CxxTemplate
Now you can list all the files in the project by typing the following command:find .
Generate our Ninja build file using the cmake command in the CxxTemplate folder. To do that, write the following command:cmake -Bbuild -H. -GNinja
The output of the preceding command is as follows:
Let's explain parts of the preceding command. With -Bbuild, we are telling CMake to use the build folder to generate build artifacts. Since this folder does not exist, CMake will create it. With –H., we are telling CMake to use the current folder as the source. By using a separate build folder, we will keep our source files clean and all the build artifacts will live in the build folder, which is ignored by Git thanks to our .gitignore file. With –GNinja, we are telling CMake to use the Ninja build system.
Run the following commands to list the project files and to check the files that were created inside the build folder:ls
ls build
The preceding command will show the following output in the terminal:
It's clear that the preceding files will be present inside the build folder. build.ninja and rules.ninja in the preceding output are the Ninja build files that can actually build our project in this platform.
By using CMake, we did not have to write the Ninja build files and avoided committing to the Unix platform. Instead, we have a meta-build system that can generate low-level build files for other platforms such as UNIX/Linux, MinGW, and Nmake.
Now, go into the build folder and build our project by typing the following commands in the terminal:cd build
ninja
You should see a final output like the following:
ls
The previous command yields the following output in the terminal:
In the preceding figure, you can see that the CxxTemplate executable is generated.
In the terminal, type the following command to run the CxxTemplate executable:./CxxTemplate
The previous command in the terminal will provide the following output:
The following line from the src/CxxTemplate.cpp file is responsible for writing the previous output:
std::cout << "Hello CMake." << std::endl;
Now you have successfully built a CMake project in Linux. Ninja and CMake work quite well together. You have to run CMake only once and Ninja will detect whether CMake should be called again and will call it for you. For example, even if you add new source files to your CMakeLists.txt file, you only need to type the ninja command in the terminal, and it will run CMake automatically for you to update the Ninja build files. Now that you have learned about building a CMake project in Linux, in the next section, we will look at how to import a CMake project into Eclipse CDT.
A Ninja build file is useful for building our project in Linux. However, a CMake project is portable and can be used with other build systems and IDEs as well. Many IDEs accept CMake as their configuration file and provide a seamless experience as you modify and build your project. In this section, we will discuss how to import a CMake project into Eclipse CDT, which is a popular cross-platform C/C++ IDE.
There are multiple ways of using Eclipse CDT with CMake. The default one that CMake provides is the one-way generation of the IDE project. Here, you create the IDE project once, and any modifications you make to your IDE project will not change back the original CMake project. This is useful if you manage your project as a CMake project and do one-time builds with Eclipse CDT. However, it's not ideal if you want to do your development in Eclipse CDT.
Another way of using CMake with Eclipse CDT is to use the custom cmake4eclipse plugin. When using this plugin, you do not abandon your CMakeLists.txt file and make a one-way switch to Eclipse CDT's own project manager. Instead, you keep managing your project through the CMakeLists.txt file, which continues to be the main configuration file of your project. Eclipse CDT actively works with your CMakeLists.txt file to build your project. You can add or remove source files and make other changes in your CMakeLists.txt, and the cmake4eclipse plugin applies those changes to the Eclipse CDT project at every build. You will have a nice IDE experience while keeping your CMake project current. The benefit of this approach is that you can always quit using Eclipse CDT and use your CMakeLists.txt file to switch to another build system (such as Ninja) later. We will use this second approach in the following exercise.
In the last exercise, you developed a CMake project and you would like to start using Eclipse CDT IDE to edit and build that project. In this exercise, we will import our CMake project into the Eclipse CDT IDE using the cmake4eclipse plugin. Perform the following steps to complete the exercise:
Open Eclipse CDT. Create a new C++ project in the location of our current project (the folder that contains the CMakeLists.txt file and the src folder). Go to File | New | Project. A New Project dialog box appears like the one in the following screenshot:You have successfully built and run a CMake project using Eclipse CDT. In the next exercise, we will introduce a frequent change to our projects by adding new source files with new classes.
As you develop significantly bigger C++ projects, you will tend to add new source files to it as the project grows to meet the set expectations. In this exercise, we will add a new .cpp and .h file pair to our project and see how CMake and Eclipse CDT work together with these changes. We will add these files inside the project using the new class wizard, but you can also create them with any other text editor. Perform the following steps to add new source files to CMake and Eclipse CDT:
First, open the project that we have been using until now. In the Project Explorer pane on the left, expand the root entry, CxxTemplate, and you will see the files and folders of our project. Right-click the src folder and select New | Class from the pop-up menu:#include "ANewClass.h"
#include <iostream>
void ANewClass::run() {
std::cout << "Hello from ANewClass." << std::endl;
}
You will see that Eclipse warns us with a Member declaration not found message:
This error is generated since we need to add this to our ANewClass.h file as well. Such warnings are made possible by analyzers in IDEs and are quite useful as they help you fix your code as you are typing, without running the compiler.
Open the ANewClass.h file, add the following code, and save the file:public:
void run(); // we added this line
ANewClass();
You should see that the error in the .cpp file went away. If it did not go away, it may be because you may have forgotten to save one of the files. You should make it a habit to press Ctrl + S to save the current file, or Shift + Ctrl + S to save all the files that you edited.
Now, let's use this class from our other class, CxxTemplate.cpp. Open that file, perform the following modifications, and save the file. Here, we are first importing header files and in the constructor of CxxApplication, we are printing text to the console. Then, we are creating a new instance of ANewClass and calling its run method:#include "CxxTemplate.h"
#include "ANewClass.h"
#include <string>
...
CxxApplication::CxxApplication( int argc, char *argv[] ) {
std::cout << "Hello CMake." << std::endl;
::ANewClass anew;
anew.run();
}
The complete code of this file can be found here: https://github.com/TrainingByPackt/Advanced-CPlusPlus/blob/master/Lesson1/Exercise03/src/CxxTemplate.cpp.
Try to build the project by clicking on the Project | Build All menu options. You will get some undefined reference errors in two lines. This is because our project is built with CMake's rules and we did not let CMake know about this new file. Open the CMakeLists.txt file, make the following modification, and save the file:add_executable(CxxTemplate
src/CxxTemplate.cpp
src/ANewClass.cpp
)
Try to build the project again. This time you should not see any errors.
Run the project using the Run | Run menu option. You should see the following output in the terminal:You modified a CMake project, added new files to it, and ran it fine. Note that we created the files in the src folder and let the CMakeLists.txt file know about the CPP file. If you do not use Eclipse, you can simply continue with the usual CMake build commands and your program will run successfully. So far, we have checked out the sample code from GitHub and built it both with plain CMake and with the Eclipse IDE. We also added a new class to the CMake project and rebuilt it in Eclipse IDE. Now you know how to build and modify CMake projects. In the next section, we will perform an activity of adding a new source-header file pair to the project.
As you develop C++ projects, you add new source files to it as the project grows. You may want to add new source files for various reasons. For example, let's say you are developing an accounting application in which you calculate interest rates in many places of your project, and you want to create a function in a separate file so that you can reuse it throughout your project. To keep things simple, here we will create a simple summation function instead. In this activity, we will add a new source-header file pair to the project. Perform the following steps to complete the activity:
Open the project that we created in the earlier exercise in the Eclipse IDE.Add the SumFunc.cpp and SumFunc.h file pair to the project. Create a simple function named sum that returns the sum of two integers.Call the function from the CxxTemplate class constructor.Build and run the project in Eclipse.The expected output should be similar to the following:
The solution for this activity can be found on page 620.
In the following section, we will talk about how to write unit tests for our projects. It is common to divide projects into many classes and functions that work together to achieve the desired goal. You must manage the behavior of these classes and functions with unit tests to ensure that they behave in expected ways.
Unit tests are an important part of programming in general. Basically, unit tests are little programs that use our classes in various scenarios with expected results, live in a parallel file hierarchy in our project, and do not end up in the actual executable but are executed separately by us during development to ensure that our code is behaving in expected ways. We should write unit tests for our C++ programs to ensure that they behave as they are supposed to after each change.
There are several C++ test frameworks that we can use with CMake. We will use Google Test, which has several benefits over other options. In the next exercise, we will prepare our project for unit testing with Google Test.
We have installed Google Test but our project is not set up to use Google Test for unit testing. In addition to the installation, there are settings that need to be carried out in our CMake project to have Google Test unit tests. Follow these steps to implement this exercise:
Open Eclipse CDT and select the CxxTemplate project that we have been using.Create a new folder named tests as we will perform all our tests there. Edit our base CMakeLists.txt file to allow tests in the tests folder. Note that we already had code to find the GTest package that brings GoogleTest capability to CMake. We will add our new lines just after that:find_package(GTest)
if(GTEST_FOUND)
set(Gtest_FOUND TRUE)
endif()
if(GTest_FOUND)
include(GoogleTest)
endif()
# add these two lines below
enable_testing()
add_subdirectory(tests)
This is all we need to add to our main CMakeLists.txt file.
Create another CMakeLists.txt file inside our tests folder. This will be used because of the add_subdirectory(tests) line that we had in our main CMakeLists.txt file. This tests/CMakeLists.txt file will manage the test sources. Add the following code in the tests/CMakeLists.txt file:include(GoogleTest)
add_executable(tests CanTest.cpp)
target_link_libraries(tests GTest::GTest)
gtest_discover_tests(tests)
Let's dissect this code line by line. The first line brings in the Google Test capability. The second line creates the tests executable, which will include all our test source files. In this case, we only have one CanTest.cpp file, which will just verify that the testing works. After that, we link the GTest library to the tests executable. The last line identifies all individual tests in the tests executable and adds them to CMake as a test. This way, various test tools will be able to tell us which individual tests failed and which ones passed.
Create a tests/CanTest.cpp file. Add this code to simply verify that tests are running, without actually testing anything in our actual project:#include "gtest/gtest.h"
namespace {
class CanTest: public ::testing::Test {};
TEST_F(CanTest, CanReallyTest) {
EXPECT_EQ(0, 0);
}
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
The TEST_F line is an individual test. Now, EXPECT_EQ(0, 0) is testing whether zero is equal to zero, which will always succeed if we can actually run the test. We will later add the results of our own classes here to be tested against various values. Now we have the necessary setup for Google Test in our project. Next, we will build and run these tests.
Now, we will discuss how to build, run, and write unit tests. The example that we have so far is a simple dummy test that is ready to be built and run. Later, we will add tests that make more sense and view the output of passing and failing tests. In the following exercise, we will build, run, and write unit tests for the project that we created in the previous exercise.
So far, you have created a project with GoogleTest set up, but you did not build or run the tests we created. In this exercise, we will build and run the tests that we created. Since we added our tests folder using add_subdirectory, building the project will automatically build the tests. Running the tests will require some more effort. Perform the following steps to complete the exercise:
Open our CMake project in Eclipse CDT.To build the tests, simply build the project just like you did before. Here is the output of building the project one more time from Eclipse after a full build using Project | Build All:As you can see, our project now has two executable targets. They both live in the build folder, as with any other build artifact. Their locations are build/Debug/CxxTemplate and build/Debug/tests/tests. Since they are executables, we can simply run them.
We ran CxxTemplate before and will not see any extra output now. Run the other executable by typing the following command in the terminal while we are in our project folder:./build/Debug/tests/tests
The preceding code generates the following output in the terminal:
This is the simple output of our tests executable. If you want to see whether the tests have passed, you can simply run this. However, testing is so much more than that.
One of the ways you can run your tests is by using the ctest command. Write the following commands in the terminal while you are in the project folder. We go to the folder where the tests executable resides, run ctest there, and come back:cd build/Debug/tests
ctest
cd ../../..
And here is the output that you will see:
The ctest command can run your tests executable with a number of options, including the ability to submit test results automatically to online dashboards. Here, we will simply run the ctest command; its further features are left as an exercise for the interested reader. You can type ctest --help or visit the online documentation to discover ctest further at https://cmake.org/cmake/help/latest/manual/ctest.1.html#.
Another way to run the tests is to run them inside Eclipse, in a nice graphical report format. For this, we will create a run configuration that is test-aware. In Eclipse, click on Run | Run Configurations…, right-click C/C++ Unit on the left, and select New Configuration.Change the name from CxxTemplate Debug to CxxTemplate Tests as follows:The result will be similar to the following screenshot:
This is a nice report that contains entries for all tests—only one for now. You may prefer this if you do not want to leave the IDE. Furthermore, when you have many tests, this interface can help you filter them effectively. Now you have built and run tests that were written using Google Test. You ran them in a couple of different ways, including directly executing the test, using ctest, and using Eclipse CDT. In the next section, we will solve an exercise wherein we will actually test the functionality of our code.
You have run simple tests but now you want to write meaningful tests that are testing functionality. In the initial activity, we created SumFunc.cpp, which had the sum function. Now, in this exercise, we will write a test for that file. In this test, we will use the sum function to add two numbers and verify that the result is correct. Let's recall the contents of the following files with the sum function from before:
src/SumFunc.h:#ifndef SRC_SUMFUNC_H_
#define SRC_SUMFUNC_H_
int sum(int a, int b);
#endif /* SRC_SUMFUNC_H_ */
src/SumFunc.cpp:#include "SumFunc.h"
#include <iostream>
int sum(int a, int b) {
return a + b;
}
Relevant lines of CMakeLists.txt:add_executable(CxxTemplate
src/CxxTemplate.cpp
src/ANewClass.cpp
src/SumFunc.cpp
)
Also, let's recall our CantTest.cpp file, which has the main() function of our unit tests:
#include "gtest/gtest.h"
namespace {
class CanTest: public ::testing::Test {};
TEST_F(CanTest, CanReallyTest) {
EXPECT_EQ(0, 0);
}
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Perform the following steps to complete the exercise:
Open our CMake project in Eclipse CDT.Add a new test source file (tests/SumFuncTest.cpp) with the following content:#include "gtest/gtest.h"
#include "../src/SumFunc.h"
namespace {
class SumFuncTest: public ::testing::Test {};
TEST_F(SumFuncTest, CanSumCorrectly) {
EXPECT_EQ(7, sum(3, 4));
}
}
Note that this does not have a main() function since CanTest.cpp has one and these will be linked together. Secondly, note that this includes SumFunc.h, which is in the src folder of the project and uses it as sum(3, 4) inside the test. This is how we use our project code in tests.
Make the following change in the tests/CMakeLists.txt file to build the test:include(GoogleTest)
add_executable(tests CanTest.cpp SumFuncTest.cpp ../src/SumFunc.cpp) # added files here
target_link_libraries(tests GTest::GTest)
gtest_discover_tests(tests)
Note that we added both the test (SumFuncTest.cpp) and the code that it tests (../src/SumFunc.cpp) to the executable, as our test code is using the code from the actual project.
Build the project and run the test as before. You should see the following report:We can add such tests to our project and all of them will appear on the screen as shown in the preceeding screenshot.
Now, let's add one more test that will actually fail. In the tests/SumFuncTest.cpp file, make the following change:TEST_F(SumFuncTest, CanSumCorrectly) {
EXPECT_EQ(7, sum(3, 4));
}
// add this test
TEST_F(SumFuncTest, CanSumAbsoluteValues) {
EXPECT_EQ(6, sum(3, -3));
}
Note that this test assumes that the absolute values of the inputs are summed up, which is incorrect. The result of this call is 0 but is expected to be 6 in this example. This is the only change that we have to make in our project to add this test.
Now, build the project and run the test. You should see this report:As you can see in the preceding figure, the first two tests passed and the last test failed. When we see this output, there are two options: either our project code is wrong, or the test is wrong. In this case, our test is wrong. This is because our CanSumAbsoluteValues test case expects that 6 is equal to sum(3, -3). This is because we assumed that our function sums up the absolute values of the integers provided. However, this is not the case. Our function simply adds the given numbers, whether they are positive or negative. Therefore, this test had a faulty assumption and failed.
Let's change the test and fix it. Change the test so that we expect the sum of -3 and 3 to be 0. Rename the test to reflect what this test actually does:TEST_F(SumFuncTest, CanSumCorrectly) {
EXPECT_EQ(7, sum(3, 4));
}
// change this part
TEST_F(SumFuncTest, CanUseNegativeValues) {
EXPECT_EQ(0, sum(3, -3));
}
Run it now and observe in the report that all the tests pass:Finally, we have set up Google Test with CMake both in our system and project. We also wrote, built, and ran unit tests with Google Test, both in the terminal and in Eclipse. Ideally, you should write unit tests for every class and cover every possible usage. You should also run the tests after each major change and make sure you do not break existing code. In the next section, we will perform an activity of adding a new class and its test.
As you develop a C++ project, you add new source files to it as the project grows. You also write tests for them to ensure that they are working properly. In this activity, we will add a new class that simulates 1D linear motion. The class will have double fields for position and velocity. It will also have a advanceTimeBy() method, which receives a double dt parameter, which modifies position based on the value of velocity. Use EXPECT_DOUBLE_EQ instead of EXPECT_EQ for double values. In this activity, we will add a new class and its test to the project. Follow these steps to perform this activity:
Open the project that we have created in the Eclipse IDE.Add the LinearMotion1D.cpp and LinearMotion1D.h file pair to the project that contains the LinearMotion1D class. In this class, create two double fields: position and velocity. Also, create an advanceTimeBy(double dt) function that modifies position.Write tests for this in the tests/LinearMotion1DTest.cpp file. Write two tests that represent motion in two different directions.Build and run it in the Eclipse IDE.Verify that the tests have passed.The final test results should look similar to the following:
The solution for this activity can be found on page 622.
Adding new classes and their tests is a very common task in C++ development. We create classes for various reasons. Sometimes, we have a nice software design plan and we create the classes that it calls for. Other times, when a class becomes too large and monolithic, we separate some of its responsibility to another class in a meaningful way. Having this task be practical is important to prevent dragging your feet and ending up with huge monolithic classes. In the following section, we discuss what happens during the compilation and linking stages. This will give us a better perspective of what is happening under the hood of C++ programs.
One of the main reasons for using C++ is efficiency. C++ gives us control over memory management, which is why understanding how objects are laid out in memory is important. Furthermore, C++ source files and libraries are compiled to object files for the target hardware and linked together. Often, C++ programmers have to deal with linker problems, which is why understanding the steps of the compilation and being able to investigate object files is important. On the other hand, large projects are developed and maintained by teams over a long period of time, which is why creating clean and understandable code is important. As with any other software, bugs arise in C++ projects and need to be identified, analyzed, and resolved carefully by observing the program behavior. Therefore, learning how to debug C++ code is also important. In the next section, we will learn how to create code that is efficient, plays well with other code, and is maintainable.
A C++ project is created as a set of source code files and project configuration files that organize the sources and library dependencies. In the compilation step, these sources are first converted to object files. In the linking step, these object files are linked together to form the executable that is the ultimate output of the project. The libraries that the project uses are also linked at this step.
In the upcoming exercises, we will use our existing project to observe the compilation and linking stages. Then, we will manually recreate them to view the process in more detail.
You have been building your projects without investigating the details of the build actions. In this exercise, we will investigate the details of our project's build steps. Perform the following to complete the exercise:
Open the terminal.Navigate to the build folder wherein our Makefile file resides by typing the following command:cd build/Debug
Clean the project and run the build in VERBOSE mode using the following command:make clean
make VERBOSE=1 all
You will get a detailed output of the build process in the terminal, which may look a bit crowded:
Here are some of the lines from this output. The following lines are the important ones related to the compilation and linkage of the main executable:
/usr/bin/c++ -g -pthread -std=gnu++1z -o CMakeFiles/CxxTemplate.dir/src/CxxTemplate.cpp.o -c /home/username/Packt/Cpp2019/CxxTemplate/src/CxxTemplate.cpp
/usr/bin/c++ -g -pthread -std=gnu++1z -o CMakeFiles/CxxTemplate.dir/src/ANewClass.cpp.o -c /home/username/Packt/Cpp2019/CxxTemplate/src/ANewClass.cpp
/usr/bin/c++ -g -pthread -std=gnu++1z -o CMakeFiles/CxxTemplate.dir/src/SumFunc.cpp.o -c /home/username/Packt/Cpp2019/CxxTemplate/src/SumFunc.cpp
/usr/bin/c++ -g -pthread -std=gnu++1z -o CMakeFiles/CxxTemplate.dir/src/LinearMotion1D.cpp.o -c /home/username/Packt/Cpp2019/CxxTemplate/src/LinearMotion1D.cpp
/usr/bin/c++ -g CMakeFiles/CxxTemplate.dir/src/CxxTemplate.cpp.o CMakeFiles/CxxTemplate.dir/src/ANewClass.cpp.o CMakeFiles/CxxTemplate.dir/src/SumFunc.cpp.o CMakeFiles/CxxTemplate.dir/src/LinearMotion1D.cpp.o -o CxxTemplate -pthread
The c++ command here is just a symbolic link to the g++ compiler. To see that it's actually a chain of symbolic links, type the following command:namei /usr/bin/c++
You will see the following output:
Therefore, we will use c++ and g++ interchangeably throughout our discussion. In the build output that we quoted earlier, the first four lines are compiling each .cpp source file and creating the corresponding .o object file. The last line is linking together these object files to create the CxxTemplate executable. The following figure visually presents this process:
As the previous figure shows, the CPP files that are added to CMake as a part of a target, along with the header files that they included, are compiled to object files, which are later linked together to create the target executable.
To understand this process even further, let's carry out the compilation steps ourselves. In the terminal, go to the project folder and create a new folder named mybuild using the following commands:cd ~/CxxTemplate
mkdir mybuild
Then, run the following commands to compile the CPP source files to object files:/usr/bin/c++ src/CxxTemplate.cpp -o mybuild/CxxTemplate.o -c
/usr/bin/c++ src/ANewClass.cpp -o mybuild/ANewClass.o -c
/usr/bin/c++ src/SumFunc.cpp -o mybuild/SumFunc.o -c
/usr/bin/c++ src/LinearMotion1D.cpp -o mybuild/LinearMotion1D.o -c
Go into the mybuild directory and see what's there using the following command:cd mybuild
ls
We see the following output as expected. These are our object files:
/usr/bin/c++ CxxTemplate.o ANewClass.o SumFunc.o LinearMotion1D.o -o CxxTemplate
Now, let's see our executable among the list of files here by typing the following command:ls
This shows the new CxxTemplate file in the following figure:
./CxxTemplate
And see the output that we had before:
Now that you have examined the details of the build process and have recreated them yourself, in the next section, let's explore the linking process.
In this section, let's look at a connection between two source files and how they end up in the same executable. Look at the sum function in the following figure:
The sum function's body is defined in SumFunc.cpp. It has a forward declaration in SumFunc.h
