Skip to content

Introduction & Environment Setup

Welcome to the Modern C++ Masterclass! C++ is one of the most widely used systems programming languages, providing both low-level hardware control and high-level abstract features. This course is specifically tailored for development on the Raspberry Pi, showing you how to build fast, robust, and modern applications.


1. What is C++? A Brief History

C++ was created by Bjarne Stroustrup at Bell Labs starting in 1979 as an extension to the C language. Originally called "C with Classes," it was renamed C++ in 1983 (where ++ is the increment operator, symbolizing progress).

Over the decades, C++ has evolved through several standardizations managed by the ISO committee:

Era Standard Key Paradigm / Main Focus
Classic C++98 / C++03 Object-Oriented Programming (OOP) and introduction of the STL (Standard Template Library).
Modern C++ C++11 A major reboot. Introduced move semantics, auto, lambdas, smart pointers, and multi-threading.
C++14 Bug fixes and minor updates to C++11 features (e.g., generic lambdas).
C++17 Structured bindings, inline variables, std::optional, std::variant, and filesystem API.
Modern Evolution C++20 Concepts, Ranges, Coroutines, Modules, and std::format.
C++23 std::print/std::println, std::expected, std::mdspan, and compiler improvements.

On platforms like the Raspberry Pi, modern C++ allows you to write high-level hardware control logic without paying a performance penalty. The core philosophy of C++ is "Zero-Overhead Abstraction" — you don't pay for what you don't use.


2. Setting up the C++ Environment on Raspberry Pi

To write modern C++, we need an up-to-date compiler toolchain. Raspberry Pi OS (Bookworm and later) is based on Debian, shipping with recent versions of GCC (GNU Compiler Collection).

Installing Build Tools

Open your terminal and run the following command to install the essential packages:

sudo apt update
sudo apt install build-essential cmake gdb git -y
  • build-essential: Installs g++ (C++ compiler), gcc (C compiler), make (build tool), and fundamental system libraries.
  • cmake: A meta-build system generator used in almost all professional C++ projects.
  • gdb: The GNU Debugger, used for diagnosing segfaults and debugging runtime execution.

Verifying GCC Compiler Support

Confirm the version of g++ installed by running:

g++ --version

Ideally, you want GCC 12 or later (shipped with Raspberry Pi OS Bookworm) to ensure solid support for C++20 and initial support for C++23.


3. The C++ Compilation Pipeline

Unlike languages like Python, C++ code is not interpreted line-by-line at runtime. Instead, it must be compiled into machine-readable binary code before execution. The compiler goes through four primary stages:

[Source Code: main.cpp]
        ▼ (Stage 1: Preprocessor - resolves #include and #define)
[Preprocessed Source: main.i]
        ▼ (Stage 2: Compiler - translates C++ syntax to CPU instructions)
[Assembly Code: main.s]
        ▼ (Stage 3: Assembler - translates assembly to machine instructions)
[Object File: main.o (ELF binary format)]
        ▼ (Stage 4: Linker - merges object files and standard libraries)
[Executable Binary: hello (runnable by OS)]

Let's examine how you can manually inspect each phase of this process.

Step 1: Preprocessing

The preprocessor resolves preprocessor directives starting with #. It copies headers into the source file and handles conditional compilation macros (e.g. #ifdef). To inspect preprocessor output, use the -E flag:

g++ -E main.cpp -o main.i
(Open main.i in a text editor to see how a simple file grows to thousands of lines after copying <iostream>).

Step 2: Compilation

The compiler parses the C++ code, optimizes it, and translates it into assembly language. To stop after the compilation phase and output assembly instructions, use the -S flag:

g++ -S main.cpp -o main.s

Step 3: Assembly

The assembler takes assembly text files and translates them into binary machine instructions, creating an object file (e.g., .o or .obj). To compile to an object file without linking, use the -c flag:

g++ -c main.cpp -o main.o

Step 4: Linking

The linker merges your object files with compiled code from runtime libraries (like libc++ or libstdc++) to resolve addresses and create the final executable.

g++ main.o -o hello

4. Analyzing Your First C++ Program

Let's analyze the components of a simple C++ program.

// file: main.cpp
#include <iostream>  // 1. Preprocessor directive

// 2. Main function: Program entry point
int main() {
    // 3. Output stream with standard namespace
    std::cout << "Hello, Modern C++ on Raspberry Pi!" << std::endl;

    // 4. Return statement indicating successful exit
    return 0; 
}

Deep Dive into Code Semantics

  • #include <iostream>: Tells the preprocessor to load the stream Input/Output header. It contains declarations for std::cout, std::cin, and std::cerr.
  • int main(): Every runnable C++ application requires a global function named main. It must return an int.
  • std::cout: Stands for "Standard Character Output". The std:: prefix indicates it belongs to the std (standard) namespace. Namespaces prevent name collisions in large projects.
  • <<: The stream insertion operator. It pushes the string on the right side into the stream on the left side (std::cout).
  • std::endl: A stream manipulator. It inserts a newline (\n) and flushes the output buffer (forcing immediate terminal output). For performance-critical code, using '\n' is preferred over std::endl to avoid redundant buffer flushing.
  • return 0;: In C++, returning 0 from the main function signals to the operating system that the program terminated successfully. Non-zero values indicate errors.

5. Compiler Flags and Optimization

When compiling code, passing appropriate flags to the compiler dictates performance, code safety, and diagnostics.

g++ -std=c++20 -Wall -Wextra -O2 main.cpp -o hello
  • -std=c++20: Explicitly instructs the compiler to build with C++20 features enabled. For C++23, use -std=c++23.
  • -Wall: Enables "all" common warning messages. High-quality code should compile with zero warnings.
  • -Wextra: Enables additional warning messages not covered by -Wall. Helpful for catching subtle logic errors.
  • -O2: Turns on compiler optimizations. The compiler optimizes CPU registers and instruction orders, making the program run significantly faster. For maximum optimizations, use -O3. During debugging, use -O0 (no optimization) or -Og (debug-friendly optimization) along with the -g flag to generate debugging symbols.

6. Project Automation with CMake

As soon as a project has more than one source file, running g++ manually becomes tedious. CMake is the industry standard tool to generate system-native build files (like Makefiles).

1. Create a Project Directory

mkdir -p my_project/src
cd my_project

2. Create the Source File

Save the "Hello, World" code into src/main.cpp.

3. Create the CMakeLists.txt

In the root directory of my_project/, create a file named CMakeLists.txt:

cmake_minimum_required(VERSION 3.15)
project(RaspberryPiCppDemo VERSION 1.0.0 LANGUAGES CXX)

# Require C++20 standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Enable common warnings for safety
if(MSVC)
    add_compile_options(/W4)
else()
    add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# Define output executable and its source files
add_executable(demo src/main.cpp)

4. Build and Run the App

Always use a separate build directory to prevent generated files from cluttering your source code:

# Create and navigate to the build directory
mkdir build && cd build

# 1. Configure the project (generates Makefiles)
cmake ..

# 2. Build the project (calls make internally)
cmake --build .

# 3. Execute the resulting binary
./demo

7. Basic Interactive Input/Output

Besides writing to standard output, a command-line program often requires reading user input. C++ provides std::cin for input streams.

#include <iostream>
#include <string>
#include <limits>

int main() {
    std::cout << "Enter your age: ";
    int age = 0;
    std::cin >> age; // Extraction operator

    // Clear input buffer to handle remaining newline
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    std::cout << "Enter your full name: ";
    std::string full_name;
    std::getline(std::cin, full_name); // Reads the whole line including spaces

    std::cout << "\nHello, " << full_name << "! You are " << age << " years old.\n";
    return 0;
}
  • std::cin >> age: Extracts data from standard input into the variable. It stops reading at whitespace.
  • std::getline(std::cin, full_name): Used to read a full line of text including spaces. Since std::cin >> leaves a newline character (\n) in the stream, we call std::cin.ignore() first to flush any remaining character in the buffer before reading the string.

  • Basic Syntax — Dig into variables, types, and modern C++ assignments.
  • Build Systems — Mastering advanced CMake layouts on Raspberry Pi.