4.1. Introduction to C++#

The C++ programming language was created by Bjarne Stroustrup and his team at Bell Laboratories to help implement simulation projects in an object-oriented and efficient way. The earliest versions, which were originally referred to as “C with classes”, date back to 1980. As the name C++ implies, C++ was derived from the C programming language: ++ is the increment operator in C. Whereas C was designed with traditional procedural programming in mind, C++ was built on the concept of Object-Oriented Programming (OOP).

As early as 1989 an ANSI Committee (American National Standards Institute) was founded to standardize the C++ programming language. The aim was to have as many compiler vendors and software developers as possible agree on a unified description of the language in order to avoid the confusion caused by a variety of dialects. In 1998 the ISO (International Organization for Standardization) approved a standard for C++ (ISO/IEC 14882).

4.1.1. What is Object-Oriented Programming (OOP)?#

In traditional procedural programming, such as C, functions (subroutines, procedures) are kept separate from the data they process, and each operation or procedure on the data must be spelled out explicitly by the programmer. This has a significant effect on the way a program handles data:

  • The programmer must ensure that data are initialized with suitable values before use and that suitable data are passed to a function when it is called.

  • If the data representation is changed, e.g. if a record is extended, the corresponding functions must also be modified.

  • Often programs have to acquire and release resources from the operating system, such Random Access Memory (RAM) memory to store data, network connections, or files on the file system. In procedural programming, the programmer is expected to call the appropriate function to acquire the resource (e.g., allocate memory, open a network port, open a file for reading), but also to call the corresponding function to release the resource (e.g., release memory, close the port, close the file). Forgetting this last step often results in a working program, but with unintended side affects, such as increasingly using more system memory, network failures, or files that cannot be opened anymore by other programs.

  • The programmer must implement all handling of errors that can occur during the execution, so called runtime errors of the program through “error codes”. When a function encounters an abnormal condition such as incorrect input, a division by zero, or an attempt to open a non-existent file, the function should terminate prematurely and indicate the specific problem to its calling function using an “error code”, basically an integer number specific for the condition. The programmer of the calling function must account for all error codes of the called functions to properly handle to the condition (e.g, inform the user, or try to open a different file). It is easy for a programmer to skip over this step, which might not even be noticed as a problem until the program is already in use and suddenly crashes.

All of these points can lead to errors and neither support low program maintenance requirements.

Object-Oriented Programming (OOP) shifts the focus of attention to the objects, that is, to the aspects on which the problem is centered.

OOP objects combine data (properties) and functions (capacities). For example, a program designed to maintain bank accounts would work with data such as balances, credit limits, transfers, interest calculations, and so on. An object representing an account in a program will have properties to represent such data, and capacities for account management, e.g. transferring money from one acount to another, or adjusting an interest rate.

Each object has class, which you can think of as an object type. A class defines both the properties and the capacities that objects of its type will have, but it is not an object itself and thus is not associated with any memory. A single class can be used to create many objects of its type, and each object will have its own copy of the data and therefore occupy memory.

Note

OOP, such as in C++, offers several advantages to software development over procedural programming:

  • Reduced susceptibility to errors: an object controls access to its own data. More specifically, an object can reject erroneous access attempts.

  • Low maintenance requirement: an object type can modify its own internal data representation without requiring changes to the application.

  • Easy re-use: objects maintain themselves and can therefore be act as building blocks for other classes. For example, classes can define how all data in their objects should be initialized jointly through special functions called constructors. This way, the programmer can ensure that an object of this class always has meaningful values for all its data fields. Similarly, classes can define other operations for its objects, ensuring the objects are well-behaved during class design. This simplifies the work for later programmers who will use the class to build more complex behavior on top, as they can use these object operations without having to worry about the object’s internal data managament anymore.

  • Resource management: Classes can also define default behaviors to clean up an object’s data once the object reaches the end of its use, through functions called destructors. This means we can use classes to represent and manage resources by relying on its constructor to acquire a resource (e.g., reserve a large chunk of memory) and its destructor to release it (e.g., free the memory such that other programs can use it), This is a powerful OOP concept called Resource Acquisition Is Initialization (RAII), and prevents memory leaks and other resource management errors.

  • Error management through exceptions: When an abnoral condition occurs during execution of the program, so a runtime error, the program can handle this abnormal condition by creating a special object called an exception which contains all relevant error information, for instance a description of the missing file name. The exception can then immediately by passed on to the calling function without using error codes. If RAII is properly used, any resources opened by the function are automatically released when this happens without the programmer needing to reimplement this explicitly in the function.

4.1.2. Scripted vs Compiled Programming Language#

C++ is a compiled language, whereas various other popular programming languages such as Python, Matlab, or Bash are interpreted.

In a compiled language, like C++, the code written by the programmer is converted into an executable using a compiler. The compiler takes the code, and turns it a standalone program consisting of low-level machine instructions suitable for the target CPU The compilation process itself can be slow, especially for large and complex programs, because the compiler will have to process and convert all code, and perform all kinds of optimizations. Although the compilation process is slow, the resulting low-level machine instructions are specific for the computer’s CPU, and therefore the most optimal type of code that the computer could execute (which of course does not mean the algorithm itself is necessarily efficient, just that it is very efficiently executed). The compilation process happens in the development stage when the programmer is building and testing the program, but once the program is finished, running the executable does not require the compiler nor any of the original C++ code to be present anymore. So, the highly optimized executable be distributed to other computers with a similar CPU architecture, without needing to distribute the C++ code or compiler tools.

In interpreted languages, like Python, the code you write as a programmer can only be executed by a separate program that understands that language, the interpreter. The interpreter runs the code without any compilation step, and therefore does not turn the code into an executable. Instead, it is the interpreter itself which is the executable, and the interpret should be installed on the system at the moment that you try to run the code. For example, to run Python code you need to have Python installed on your computer, and to run a bash script your computer must have bash (which comes installed by default on Ubuntu Linux, but not on Windows for example). You can think of an interpreter as a translator which on-the-fly converts the instructions from that programming language into corresponding CPU instructions, but the interpreter usually also takes care of allocating and releasing memory for variables, executes safety checks to avoid memory corruption, locating external libraries (e.g. numpy), etc. This often simplifies the workflow for you as a developer, but results typically in slower code, and there can be subtle variations in execution time and memory consumption as the interpreter independently performs regular background checks to clear unused memory or close unneeded files.

We often call a simple program in an interpreted languages a script. A script is a set of instructions (so, code) that tells another program how to behave. For example, a bash script is a set of instructions for the bash interpreter, and a python script for the python interpreter.

But C++ code is turned into a standalone executable by the compiler, and therefore the code itself is not needed anymore when executing the program. We refer to C++ as source code, since the executable is derived from it through the compilation process. Therefore, C++ code is not referred to as a “script”.

Note

In Robotics, it often makes sense to use “high-level” interpreted programming language like Python to implement high-level behavior, or to startup different components of the system. It is okay if such operations take a bit longer, because they are not part of any high-frequency control loop. Scripting language like Python are easy to use and provide flexibility to customize such behaviors.

On the other hand, for algorithms that are part of the main control loop, e.g., which process sensor data, filter measurements, or perform low-level control, it is important that this is done efficiently (both in terms of memory usage and latency) and reliably (so, without sudden ‘hiccups’). Such processes, often implemented as ROS nodes, are best implemented in a compiled language such as C++.

4.1.3. Editor and compiler#

Writing code in C++ can be done in any text editor, so you can use your favorite editor. It is however recommendable to use an editor with syntax highlighting, such as gedit (also often called simply ‘Text Editor’ on Ubuntu), or even nano in the terminal.

Besides an editor, you will need a compiler. A compiler that is often used and has versions for multiple operating systems is g++, which is part of the build-essential package in Ubuntu. You will also need the cmake package.

Note

If you perform these exercises on a clean Linux installation, such as Ubuntu, make sure you have the build-essential and cmake package installed via your distribution’s package manager. These will provide all the tools necessary to build your own C++ programs, as you will do in this manual. If you have not installed them yet, install them before continuing.