Working With Miscellaneous Topics in C++

In this tutorial, we will explore miscellaneous useful features in C++.

Prerequisites

Defining Variables

Let’s cover how to define variables in C++. Understanding variable types and how to use them effectively is fundamental for programming in robotics, where you need to handle various types of data, from sensor readings to actuator commands.

Defining Enums 

Let’s explore enums in C++, which allow you to create named constants for better code readability and type safety in robotics applications.

Open a terminal window, and type this: 

cd ~/Documents/cpp_tutorial 
code . 

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

Type the following code into the editor:

#include <iostream>

enum class RobotState {
    IDLE,
    MOVING,
    GRASPING,
    ERROR
};

void print_robot_state(RobotState state) {
    switch (state) {
        case RobotState::IDLE:
            std::cout << "Robot is idle." << std::endl;
            break;
        case RobotState::MOVING:
            std::cout << "Robot is moving." << std::endl;
            break;
        case RobotState::GRASPING:
            std::cout << "Robot is grasping an object." << std::endl;
            break;
        case RobotState::ERROR:
            std::cout << "Robot encountered an error." << std::endl;
            break;
    }
}

int main() {
    RobotState current_state = RobotState::IDLE;
    print_robot_state(current_state);

    current_state = RobotState::MOVING;
    print_robot_state(current_state);

    return 0;
}

In this code, we include the necessary header: iostream for input/output operations.

We define an enum class named RobotState to represent different states of a robot. The enum constants are IDLE, MOVING, GRASPING, and ERROR. Using an enum class provides strong typing and prevents naming conflicts.

We define a function print_robot_state() that takes a RobotState as a parameter. Inside the function, we use a switch statement to match the state and print the corresponding message.

In the main function, we create a variable current_state of type RobotState and initialize it with RobotState::IDLE. We pass this state to the print_robot_state() function, which prints the appropriate message.

We then change the current_state to RobotState::MOVING and call print_robot_state() again with the updated state.

Run the code.

1-enum-example

You should see the messages corresponding to the idle and moving states of the robot printed in the terminal.

Enums provide a way to create named constants, making your code more expressive and self-explanatory. They are particularly useful for representing states, modes, or options in robotic systems, improving code readability and reducing the chances of errors.

Generating Random Numbers

Let’s explore how to generate random numbers in C++, a useful technique for various robotics applications, such as simulations, sensor noise modeling, and path planning algorithms.

Let’s create a new C++ file called random_numbers_example.cpp.

Type the following code into the editor:

#include <iostream>
#include <random>
#include <ctime>

int main() {
    // Seed the random number generator
    std::srand(static_cast<unsigned int>(std::time(nullptr)));

    // Generate random integers
    int random_int = std::rand();
    std::cout << "Random integer: " << random_int << std::endl;

    // Generate random floats between 0 and 1
    float random_float = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
    std::cout << "Random float between 0 and 1: " << random_float << std::endl;

    // Generate random numbers within a range
    int min_value = 10;
    int max_value = 50;
    int random_range = min_value + (std::rand() % (max_value - min_value + 1));
    std::cout << "Random integer between " << min_value << " and " << max_value << ": " << random_range << std::endl;

    // Using the Mersenne Twister random number engine
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<double> dis(0.0, 1.0);
    double random_double = dis(gen);
    std::cout << "Random double between 0 and 1 (Mersenne Twister): " << random_double << std::endl;

    return 0;
}

In this example, we include the necessary headers: iostream for input/output operations, random for the Mersenne Twister random number engine, and ctime for seeding the random number generator.

First, we seed the random number generator using std::srand and the current time from std::time(nullptr). This ensures that we get a different sequence of random numbers each time we run the program.

We then generate a random integer using std::rand() and print it to the console.

Next, we generate a random float between 0 and 1 by dividing a random integer from std::rand() by the maximum value RAND_MAX.

To generate a random number within a specific range, we use the modulo operator % to get a random value between min_value and max_value and add min_value to shift the range.

For more advanced random number generation, we use the Mersenne Twister algorithm, which provides better statistical properties than the standard std::rand() function.

We create a std::random_device object rd to obtain a seed value, initialize a std::mt19937 generator gen with the seed, and create a std::uniform_real_distribution object dis to generate random doubles between 0 and 1. 

Finally, we generate a random double using dis(gen) and print it to the console.

Run the code.

2-random-numbers-example

You will see the random integers, floats, and doubles generated using different techniques printed in the terminal.

We’ve covered a lot of ground in these tutorials, from basic C++ concepts to advanced topics. Each of these features plays an important role in robotics programming, whether you’re working on motion control, sensor processing, or complex autonomous systems. 

Remember that writing good robotics code is about more than just making things work – it’s about writing clean, efficient, and maintainable code that can be reliably deployed in real-world applications. 

Don’t be afraid to experiment with combining different features to solve complex problems. The skills you’ve learned in these C++ tutorials will serve as a strong foundation for your robotics programming journey.

Thanks for following along with these tutorials.

Keep building!

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!