How to Use Multithreading and Time Functions in C++

In this tutorial, we will explore multithreading and time functions in C++.

Prerequisites

Implementing Threads

Let’s explore how to implement threads in C++ for robotics projects. Threads allow multiple tasks to run simultaneously, which is essential for parallel processing in robotics applications. 

Open a terminal window, and type this: 

cd ~/Documents/cpp_tutorial 
code . 

Create a new C++ file and name it threads.cpp

Type the following code into the editor:

#include <iostream>
#include <thread>
#include <chrono>

void sensorTask(const std::string& sensorName, int duration) {
    std::cout << "Sensor task started: " << sensorName << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(duration));
    std::cout << "Sensor task completed: " << sensorName << std::endl;
}

void controlTask(const std::string& controllerName, int duration) {
    std::cout << "Control task started: " << controllerName << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(duration));
    std::cout << "Control task completed: " << controllerName << std::endl;
}

int main() {
    std::cout << "Main thread started" << std::endl;

    // Create threads for sensor tasks
    std::thread sensorThread1(sensorTask, "TemperatureSensor", 2);
    std::thread sensorThread2(sensorTask, "HumiditySensor", 3);

    // Create a thread for control task
    std::thread controlThread(controlTask, "MotionController", 4);

    // Wait for the threads to finish
    sensorThread1.join();
    sensorThread2.join();
    controlThread.join();

    std::cout << "Main thread completed" << std::endl;

    return 0;
}

In this code, we define two functions: sensorTask() and controlTask(). These functions simulate tasks performed by sensors and controllers in a robotics system. Each task prints a message when it starts and completes, and sleeps for a specified duration using std::this_thread::sleep_for().

In the main() function, we create three threads:

  • Two threads for sensor tasks: sensorThread1 and sensorThread2. These threads execute the sensorTask() function with different sensor names and durations.
  • One thread for the control task: controlThread. This thread executes the controlTask() function with a controller name and duration.

After creating the threads, we use join() to wait for each thread to finish before proceeding further in the main thread.

Run the code.

1-threads

The output displays the messages from each thread, indicating when they start and complete. The threads execute concurrently, and the main thread waits for all the threads to finish before completing.

Using Time Functions

Let’s explore how to use time functions in C++ to enhance your robotics projects. Time functions are essential for tasks such as measuring elapsed time, creating delays, and synchronizing actions in robotic systems.

We will use the chrono library, which provides a set of convenient functions to work with time in C++.

Create a new C++ file and name it time_example.cpp.

Type the following code into the editor:

#include <iostream>
#include <chrono>
#include <thread>

int main() {
    auto start_time = std::chrono::steady_clock::now();

    std::this_thread::sleep_for(std::chrono::seconds(2));

    auto end_time = std::chrono::steady_clock::now();
    std::chrono::duration<double> elapsed_seconds = end_time - start_time;

    std::cout << "Elapsed time: " << elapsed_seconds.count() << " seconds\n";
    return 0;
}

In this code, we include the necessary headers: iostream for input/output operations, chrono for time functions, and thread for creating delays. 

Then, in the main function, we capture the start time using std::chrono::steady_clock::now().

Next, we create a 2-second delay using std::this_thread::sleep_for(std::chrono::seconds(2)).

Finally, we capture the end time and calculate the elapsed time by subtracting start_time from end_time. We then print the elapsed time using std::cout.

Run the code.

2-time-example

You should see the elapsed time printed in the terminal.

Thanks, and I’ll see you in the next tutorial.

Keep building!

How to Use Memory Management and Smart Pointers in C++

In this tutorial, we will explore memory management and smart pointers in C++. 

Prerequisites

Managing Memory with malloc 

Let’s explore memory management in C++ using malloc and free. Understanding manual memory management is crucial for robotics programming, especially in resource-constrained environments. 

Open a terminal window, and type this: 

cd ~/Documents/cpp_tutorial 
code . 

Create a new C++ file and name it memory_management_example.cpp

Type the following code into the editor:

#include <iostream>
#include <cstdlib>  // For malloc and free

int main() {
    int *ptr = (int*) malloc(sizeof(int));  // Allocating memory for an integer
    if (ptr == nullptr) {
        std::cout << "Memory allocation failed" << std::endl;
        return -1;  // Return an error if memory allocation failed
    }
    
    *ptr = 5;  // Assigning value to the allocated memory
    std::cout << "Value at pointer: " << *ptr << std::endl;

    free(ptr);  // Freeing the allocated memory
    ptr = nullptr;  // Setting pointer to nullptr after freeing memory

    return 0;
}

In this code snippet, we use malloc to allocate memory for an integer and check if the memory allocation was successful. We then assign a value to this memory, print it, and finally, free the memory using free to avoid memory leaks. 

Setting the pointer to nullptr after freeing is a good practice to prevent dangling pointers.

Run the code.

1-memory-management

You should see the output “Value at pointer: 5”, confirming that our memory management operations are functioning correctly.

Using Smart Pointers

Let’s explore smart pointers in C++, which provide automatic memory management and help prevent memory leaks. We’ll look at unique_ptr, shared_ptr, and weak_ptr, which are part of modern C++’s memory management toolkit. 

Create a new C++ file and name it smart_pointers.cpp

Type the following code into the editor:

#include <iostream>
#include <memory>

// Sensor class representing a sensor with a name and a value
class Sensor {
private:
    std::string name;
    double value;

public:
    Sensor(const std::string& name, double value) : name(name), value(value) {}

    void printInfo() const {
        std::cout << "Sensor: " << name << ", Value: " << value << std::endl;
    }
};

int main() {
    // Create a unique_ptr to a Sensor object
    std::unique_ptr<Sensor> sensor1 = std::make_unique<Sensor>("TemperatureSensor", 25.5);
    sensor1->printInfo();

    // Create a shared_ptr to a Sensor object
    std::shared_ptr<Sensor> sensor2 = std::make_shared<Sensor>("HumiditySensor", 60.0);
    sensor2->printInfo();

    // Create a weak_ptr to the shared_ptr
    std::weak_ptr<Sensor> weak_sensor = sensor2;
    if (auto shared_sensor = weak_sensor.lock()) {
        shared_sensor->printInfo();
    }

    return 0;
}

In this code, we define a Sensor class that represents a sensor with a name and a value.

In the main() function, we demonstrate the usage of different smart pointers:

  • unique_ptr: We create a unique_ptr to a Sensor object using std::make_unique(). The unique_ptr ensures exclusive ownership and automatically deletes the object when it goes out of scope.
  • shared_ptr: We create a shared_ptr to a Sensor object using std::make_shared(). The shared_ptr allows multiple pointers to share ownership of the object. The object is deleted when all shared_ptr instances go out of scope.
  • weak_ptr: We create a weak_ptr to the shared_ptr. The weak_ptr does not participate in ownership but can be used to check if the object is still valid. We use lock() to obtain a shared_ptr from the weak_ptr and access the object.

Run the code.

2-smart-pointers

The output displays the information of the Sensor objects created using smart pointers.

This example demonstrates how to use smart pointers in C++ for robotics projects. Smart pointers provide automatic memory management, helping to prevent memory leaks and simplify memory ownership. They are particularly useful when dealing with dynamically allocated objects and help make the code more robust and maintainable.

Thanks, and I’ll see you in the next tutorial.

Keep building!

How to Use Templates and Macros in C++

In this tutorial, we will explore templates and macros in C++.

Prerequisites

Employing Macros

Let’s explore how to use macros in C++ and their application in robotics projects. Macros are preprocessor directives that allow you to define reusable pieces of code, processed before compilation. 

Open a terminal window, and type this: 

cd ~/Documents/cpp_tutorial && code . 

Create a new C++ file and name it basic_macro.cpp.

Type the following code into the editor:

#include <iostream>

// Define a constant macro
#define PI 3.14159

// Define a function-like macro
#define AREA_CIRCLE(radius) (PI * (radius) * (radius))

int main() {
    double radius = 5.0;
    double area = AREA_CIRCLE(radius);

    std::cout << "The area of a circle with radius " << radius << " is: " << area << std::endl;

    return 0;
}

In this code, we define two macros:

  1. PI is a constant macro that defines the value of pi.
  2. AREA_CIRCLE(radius) is a function-like macro that calculates the area of a circle given its radius.

In the main() function, we use the AREA_CIRCLE macro to calculate the area of a circle with a radius of 5.0 and store the result in the area variable. We then print the calculated area using std::cout.

Run the code.

1-basic-macro

The output displays the calculated area of the circle using the AREA_CIRCLE macro.

It’s important to note that macros should be used sparingly and with caution, as they can sometimes lead to unexpected behavior if not used carefully. In modern C++, const variables, inline functions, or templates are often preferred over macros when possible.

Implementing Template Functions

Let’s explore template functions in C++, which allow us to write generic functions that can work with different data types. This is particularly useful in robotics when dealing with various sensor data types or mathematical operations. 

Create a new C++ file and name it template_functions_example.cpp

Type the following code into the editor:

#include <iostream>

template<typename T>
T find_max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    std::cout << "Max of 10 and 20 is: " << find_max<int>(10, 20) << std::endl;
    std::cout << "Max of 5.5 and 2.1 is: " << find_max<double>(5.5, 2.1) << std::endl;
    return 0;
}

In this code, we define a template function find_max that takes two parameters of the same type and returns the greater of the two. The function uses the ternary operator to compare the two values. We then test this function with integers and doubles to show its versatility.

Run the code.

2-template-functions-example

The output should display “Max of 10 and 20 is: 20” and “Max of 5.5 and 2.1 is: 5.5”, demonstrating how the template function adapts to different data types.

Defining Template Classes

Let’s explore template classes in C++, which allow us to create generic classes that can work with different data types. This is particularly useful for creating reusable data structures in robotics applications. 

Create a new C++ file and name it template_class.cpp.

Type the following code into the editor:

#include <iostream>

// Template class for a point in 2D space
template <typename T>
class Point {
private:
    T x;
    T y;

public:
    Point(T x, T y) : x(x), y(y) {}

    T getX() const { return x; }
    T getY() const { return y; }

    void printPoint() const {
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
};

int main() {
    Point<int> int_point(5, 10);
    Point<double> double_point(3.14, 2.71);

    std::cout << "Integer point: ";
    int_point.printPoint();

    std::cout << "Double point: ";
    double_point.printPoint();

    return 0;
}

In this code, we define a template class called Point that represents a point in 2D space. The class has two private member variables, x and y, of type T. The typename keyword is used to specify that T is a type parameter.

The Point class has a constructor that takes x and y values and initializes the member variables. It also provides getter functions getX() and getY() to access the values of x and y, respectively. The printPoint() function is a member function that prints the point in the format (x, y).

In the main() function, we create two instances of the Point class: int_point with integer values and double_point with double values. 

We use the printPoint() function to print the points and verify that the template class works correctly with different data types.

Run the code.

3-template-class

The output displays the points created with integer and double values.

Template classes provide flexibility and help reduce code duplication, making the code more maintainable and efficient.

Thanks, and I’ll see you in the next tutorial.

Keep building!