Tag: c++

  • Design Patterns in C/C++: Streamlining Your Code and Enhancing Your Development Skills

    A lot has been written on design patterns, but they were woefully absent in my education as a developer. I learned about them after a few years as a developer, and they were game-changing for me. In this post, I want to share with you a few of the most useful design patterns for C/C++ that I often use in my own designs.

    summary design patterns for software development
    Image courtesy of Jason McDonald

    What are Design Patterns?

    When I first learned about design patterns, they were presented to me as the proper way to organize classes to solve specific types of problems. In fact, the first design pattern I was introduced to was the state pattern for implementing state machines. Up until that point, I was resorting to complex case statements with lots of conditional checks to implement my state machine logic, simply because that was all I knew.

    I was taught that I could encapsulate all the transition logic into state classes. I was blown away by how much that single refactor could simplify my code, and how much easier it was to add and remove states.

    Over time, I started to find that the state pattern applied to so many other problems, and then that got me into trouble. I had this shiny new tool in my toolbox and I just wanted to use it for everything!

    When Should Patterns Be Applied?

    Thanks to some great mentors, I was able to step back and start looking at other patterns that existed — the factory pattern, adapters, singletons, and strategies. All of these different tools could be used in my designs to solve different problems.

    And that is the key — design patterns are tools, nothing more. As with any tool in your toolbox, there is a time and place to use it. Design patterns are no different. For C/C++ developers, they can lead to some strikingly simple and elegant solutions, but sometimes they can be a rabbit hole that will just add unnecessary abstraction to your code when a simple, straightforward solution would do.

    As conscious developers, our goal is to design amazing applications, using the best tools available to us. We understand that if a certain tool does not work for a given problem, we move on. We don’t try to force a tool on a design, which is one of the major pitfalls for new developers once they learn about design patterns.

    Types of Design Patterns

    The literature on design patterns typically breaks all the common patterns down into three primary categories, each of which deal with a different aspect of the development process:

    1. Creational Patterns
    2. Structural Patterns
    3. Behavioral Patterns

    I’m going to share with you a few of my favorite design patterns from each category. You can find tons of information about design patterns through a simple search, however one resource I have found extremely valuable is refactoring.guru. That site has lots of good information, all of which can be applied to being a conscious developer!

    Creational Design Patterns

    Creational design patterns are used in software development to provide a way to create objects in a flexible and reusable way. They help to encapsulate the creation process of an object, decoupling it from the rest of the system. This makes it easier to change the way objects are created or to switch between different implementations.

    Creational design patterns provide a variety of techniques for object creation, such as abstracting the creation process into a separate class, using a factory method to create objects, or using a prototype to create new instances. These patterns are useful when the creation of objects involves complex logic or when the object creation process needs to be controlled by the system.

    Some examples of creational design patterns include the Singleton pattern, which ensures that only one instance of a class is created, and the Factory pattern, which provides a way to create objects without specifying the exact class of object that will be created.

    Overall, creational design patterns help to improve the flexibility and reusability of software systems by providing a more structured and standardized approach to object creation.

    Singleton Creational Pattern

    The Singleton design pattern is a creational pattern that ensures that a class has only one instance and provides a global point of access to that instance. This pattern is useful in situations where only one instance of a class should exist in the program, such as managing system resources or ensuring thread safety. However, it should be used with caution, as it can introduce global state and make testing more difficult.

    UML describing the Singleton Creational Pattern

    In C++, this can be achieved by doing two things:

    1. Make the constructor for your class private.
    2. Provide a static method to access the singleton instance.

    The static method creates the singleton instance the first time it is called and returns it on subsequent calls. Ultimately, this static method typically returns either a reference to the newly created class, or a pointer to it. I prefer to use references because they are safer, but there have been cases where I create a std::shared_ptr in my static function and return that to the caller to access the singleton.

    C++ Example of the Singleton Creational Pattern

    To implement the Singleton pattern in C++, a common approach is to define a static method in the class definition. Then, in the implementation file, define the static method and have it define a static member variable of the class type. The static method will initialize the singleton instance if it has not been created yet, and return it otherwise. Here’s an example:

    class Singleton {
    public:
      static Singleton& getInstance() {
        static Singleton instance; // The singleton instance is created on the first call to getInstance()
        return instance;
      }
    
    private:
      Singleton() {} // The constructor is private to prevent typical instantiation
      Singleton(const Singleton&) = delete; // Delete the copy constructor to prevent copying
      Singleton& operator=(const Singleton&) = delete; // Delete the assignment operator to prevent copying also
    };
    
    Code language: C++ (cpp)

    In this example, the getInstance() method returns a reference to the Singleton instance, creating it on the first call using a static variable. The constructor is private to prevent instantiation from outside the class. The copy constructor and assignment operator are deleted to prevent copying.

    Your getInstance() method can take arguments as well and pass them along to the constructor, if that is desired. You need to be aware that those are only used to create the object the first time, and never again. For this reason, I consider it to be best practice for getInstance() to not take any arguments to not confuse a user making a call.

    Abstract Factory Creational Pattern

    The Abstract Factory design pattern is a creational pattern that provides an interface for creating families of related objects without specifying their concrete classes. This pattern is useful in situations where there are multiple families of related objects, and the actual type of object to be created should be determined at runtime. For example, in a messaging application, there might be multiple types of messages that need to be created. Each type of message should be consistent in interface, but with specific behavior.

    UML describing a modified Abstract Factory Creational Pattern

    C++ Example of the Abstract Factory Creational Pattern

    In C++, this can be achieved by defining an abstract base class for each family of related objects. Then define concrete subclasses for each type of object in each family. Here’s an example from a recent project of mine:

    struct Event {
      std::string m_uuid;                  // define a UUID for the event
      std::string m_msg;                   // message related to the event
      std::chrono::time_point m_timestamp; // time event occurred
    
      virtual ~Event () {}
      virtual void handle() = 0;
      virtual void clear() = 0;
    };
    
    struct ErrorEvent : public Event {
      uint32_t m_id;  // Error ID for the specific error that occurred
    
      void handle() override {
        // Perform "handling" action specific to ErrorEvent 
      }
      void clear() override {
        // Perform "clear" action specific to ErrorEvent 
      }
    };
    
    struct StateChange : public Event {
      uint32_t m_state; // state identifier
    
      void handle() override {
        // Perform "handling" action specific to StateChange 
      }
      void clear() override {
        // Perform "clear" action specific to StateChange 
      }
    };
    
    class Component {
    public:
      template <
          class T,
          std::enable_if_t<std::is_base_of_v<Event, T>, bool> = true>
      std::shared_ptr<T> GetEvent(void)
      {
        return std::make_shared<T>();
      }
    };
    Code language: C++ (cpp)

    In this example, the Event base class defines virtual methods for handling the event and clearing the event. The ErrotEvent and StateChange classes are concrete subclasses that implement these methods for the specific events. The Component class defines a GetEvent() methods for creating Events. Now, when I want to create a new event, I just inherit the Event base class and can call GetEvent() to create a new instance of the event.

    Pay special attention to lines 37. This ensures that the compiler will give an error if I try to call GetEvent with a data type that is not derived from my Event class. And that is ideal — turning what were once run time errors into compiler errors.

    This is just one use of the Abstract Factory pattern. There are others that allow you to define specific behavior for different platforms or architectures as well!

    Structural Design Patterns

    Structural design patterns are used in software development to solve problems related to object composition and structure. These patterns help to simplify the design of a software system by defining how objects are connected to each other and how they interact.

    Structural design patterns provide a variety of techniques for object composition, such as using inheritance, aggregation, or composition to create complex objects. They also provide ways to add new functionality to existing objects without modifying their structure.

    Some examples of structural design patterns include the Adapter pattern, which allows incompatible objects to work together by providing a common interface, and the Decorator pattern, which adds new behavior to an object by wrapping it with another object.

    Overall, structural design patterns help to improve the modularity, extensibility, and maintainability of software systems by providing a more flexible and adaptable way to compose objects and structures. They are particularly useful in large and complex software systems where managing the relationships between objects can become challenging.

    Adapter Structural Pattern

    The Adapter design pattern is a structural pattern that allows incompatible interfaces to work together. In C++, this pattern is used when a class’s interface doesn’t match the interface that a client is expecting. An adapter class is then used to adapt between the two interfaces.

    UML describing the Adapter Structural Pattern

    C++ Example of the Adapter Structural Pattern

    To use this in C++, define a class that implements the interface that the client expects, and internally use an instance of the incompatible class that needs to be adapted. Here’s an example:

    class ExpectedInterface {
    public:
      virtual ~ExpectedInterface() {}
      virtual void request() = 0;
    };
    
    class IncompatibleInterface {
    public:
      void specificRequest() {
        // Perform some specific request
      }
    };
    
    class Adapter : public ExpectedInterface {
    public:
      Adapter(IncompatibleInterface* adaptee) : adaptee_(adaptee) {}
    
      void request() override {
        adaptee_->specificRequest();
      }
    
    private:
      IncompatibleInterface* adaptee_;
    };
    
    Code language: C++ (cpp)

    In this example, the ExpectedInterface class defines the interface that the client expects, which is the request() method. The IncompatibleInterface class has a method called specificRequest() that is not compatible with the ExpectedInterface. The Adapter class implements the ExpectedInterface and internally uses an instance of the IncompatibleInterface class to make the specificRequest() method compatible with the ExpectedInterface.

    Using the Adapter pattern allows us to reuse existing code that doesn’t match the interface that the client expects, without having to modify that code. Instead, we can write an adapter class that mediates between the incompatible interface and the client’s expected interface.

    Decorator Structural Pattern

    The Decorator design pattern is a structural pattern that allows adding behavior or functionality to an object, without affecting other objects of the same class. This pattern is commonly used to attach additional responsibilities to an object by wrapping it with a decorator object.

    UML describing the Decorator Structural Pattern

    C++ Example of the Decorator Structural Pattern

    To use this pattern in C++, define an abstract base class that defines the interface for both the component and the decorator classes. Then define a concrete component class that implements the base interface and a decorator class that also implements the same interface and holds a pointer to the component object it is decorating. Here’s an example:

    class Component {
    public:
      virtual ~Component() {}
      virtual void operation() = 0;
    };
    
    class ConcreteComponent : public Component {
    public:
      void operation() override {
        // Perform some operation
      }
    };
    
    class Decorator : public Component {
    public:
      Decorator(Component* component) : component_(component) {}
    
      void operation() override {
        component_->operation();
      }
    
    private:
      Component* component_;
    };
    
    class ConcreteDecoratorA : public Decorator {
    public:
      ConcreteDecoratorA(Component* component) : Decorator(component) {}
    
      void operation() override {
        Decorator::operation();
        // Add some additional operation
      }
    };
    
    class ConcreteDecoratorB : public Decorator {
    public:
      ConcreteDecoratorB(Component* component) : Decorator(component) {}
    
      void operation() override {
        // Do not call base class operation() to remove that functionality from this instance
        // Add some different additional operation
      }
    };
    Code language: C++ (cpp)

    In this example, the Component class defines the interface that the concrete component and decorator classes will implement. The ConcreteComponent class is a concrete implementation of the Component interface. The Decorator class is an abstract class that also implements the Component interface and holds a pointer to a component object it is decorating. The ConcreteDecoratorA and ConcreteDecoratorB classes are concrete decorators that add additional behavior to the ConcreteComponent object by calling the Decorator base class’s operation() method and adding their own additional behavior.

    Note that if you only needed a single decorator (i.e., you did not require decorators A and B), you can get away with simply adding the necessary additional operations directly to the Decorator class in the example. However, following the pattern as shown requires not much more work immediately and will make it easier when you need to define additional concrete decorators down the road.

    Using the Decorator pattern allows us to add or remove behavior from an object without affecting other objects of the same class. It also allows us to use composition instead of inheritance to extend the functionality of an object.

    Facade Structural Pattern

    The Facade design pattern is a structural pattern that provides a simplified interface to a complex subsystem of classes, making it easier to use and understand. A Facade class can then be used by clients to access the subsystem without having to know about the complexity of the lower-level classes.

    UML describing the Facade Structural Pattern

    C++ Example of the Facade Structural Pattern

    In C++, this pattern can be used to create a high-level interface that hides the complexity of the lower-level subsystem. To implement this pattern in C++, we can define a Facade class that provides a simplified interface to the subsystem classes. Here’s an example:

    class SubsystemA {
    public:
      void operationA() {
        // Perform some operation
      }
    };
    
    class SubsystemB {
    public:
      void operationB() {
        // Perform some operation
      }
    };
    
    class Facade {
    public:
      Facade() : subsystemA_(), subsystemB_() {}
    
      void operation() {
        subsystemA_.operationA();
        subsystemB_.operationB();
      }
    
    private:
      SubsystemA subsystemA_;
      SubsystemB subsystemB_;
    };
    
    Code language: C++ (cpp)

    In this example, the SubsystemA and SubsystemB classes represent the complex subsystem that the Facade class will simplify. The Facade class provides a simplified interface to the subsystem by hiding the complexity of the lower-level classes. The Facade class also holds instances of the subsystem classes and calls their methods to perform the operation.

    This pattern allows us to simplify the interface to a complex subsystem, making it easier to use and understand. But, my favorite use of it is to isolate clients from changes to the subsystems by abstraction.

    Behavioral Design Patterns

    Behavioral design patterns are used in software development to address problems related to object communication and behavior. These patterns provide solutions for managing the interactions between objects and for coordinating their behavior.

    Behavioral design patterns provide a variety of techniques for object communication, such as using message passing, delegation, or collaboration to manage the interactions between objects. They also provide ways to manage the behavior of objects by defining how they respond to events or changes in the system.

    Some examples of behavioral design patterns include the Observer pattern, which allows objects to be notified when a change occurs in another object, and the Command pattern, which encapsulates a request as an object, allowing it to be parameterized and queued.

    Overall, behavioral design patterns help to improve the flexibility, modularity, and extensibility of software systems by providing a more structured and standardized way to manage object communication and behavior. They are particularly useful in systems that involve complex interactions between objects, such as user interfaces, network protocols, or event-driven systems.

    Mediator Behavioral Pattern

    The Mediator design pattern is a behavioral pattern that promotes loose coupling between objects by encapsulating their communication through a mediator object. In C++, this pattern can be used to reduce dependencies between objects that communicate with each other.

    UML describing the Mediator Behavioral Pattern

    C++ Example of the Mediator Behavioral Pattern

    To implement the Mediator pattern in C++, we can define a Mediator class that knows about all the objects that need to communicate with each other. The Mediator class then provides a centralized interface for these objects to communicate through. Here’s an example:

    class Component;
    
    class Mediator {
    public:
      virtual void sendMessage(Component* sender, const std::string& message) = 0;
    };
    
    class Component{
    public:
      Component(Mediator* mediator) : mediator_(mediator) {}
    
      virtual void receiveMessage(const std::string& message) = 0;
    
    protected:
      Mediator* mediator_;
    };
    
    class ConcreteComponentA : public Component{
    public:
      ConcreteComponentA(Mediator* mediator) : Component(mediator) {}
    
      void send(const std::string& message) {
        mediator_->sendMessage(this, message);
      }
    
      void receiveMessage(const std::string& message) override {
        // Process the message
      }
    };
    
    class ConcreteComponentB : public Component{
    public:
      ConcreteComponentB(Mediator* mediator) : Component(mediator) {}
    
      void send(const std::string& message) {
        mediator_->sendMessage(this, message);
      }
    
      void receiveMessage(const std::string& message) override {
        // Process the message
      }
    };
    
    class ConcreteMediator : public Mediator {
    public:
      void addComponent(Component* component) {
        components_.push_back(component);
      }
    
      void sendMessage(Component* sender, const std::string& message) override {
        for (auto component: components_) {
          if (component != sender) {
            component->receiveMessage(message);
          }
        }
      }
    
    private:
      std::vector<Component *> components_;
    };
    Code language: C++ (cpp)

    In this example, the Component classes represent objects that need to communicate with each other. The Mediator class provides a centralized interface for the Component classes to communicate through. The ConcreteMediator class knows about all the Component objects and provides the sendMessage() method to send messages between them.

    I like to use this pattern in a system where I have multiple components, such as interfaces to external hardware modules or subsystems. Those interface classes will inherit the Component base class and communicate one with another via the Mediator. In this manner, if one Component changes its interface, then I don’t need to go change all other Component classes — just the changing Component and the necessary portions of the Mediator.

    Using the Mediator pattern allows us to reduce dependencies between objects that communicate with each other, making our code more maintainable and easier to understand. It also promotes loose coupling between objects, which makes it easier to change the way objects communicate without affecting other parts of the system.

    Strategy Behavioral Pattern

    The Strategy design pattern is a behavioral pattern that defines a family of algorithms, encapsulates each algorithm, and makes them interchangeable at runtime. This pattern allows the algorithms to vary independently from clients that use them.

    I have found this pattern extremely useful when I have an application that needs to support many protocols to various clients. Using the strategy pattern, I can easily swap which protocol is in use at any given time based on the client connection.

    UML describing the Strategy Behavioral Pattern

    C++ Example of the Strategy Behavioral Pattern

    To implement this pattern, we define an abstract Strategy class that represents the interface for all algorithms. Then, we can define concrete implementations of the Strategy class for each algorithm. In my protocol case, the Strategy class was my base Protocol class. Then I had concrete protocol classes derived from the base Protocol.

    Here’s an example:

    class Strategy {
    public:
      virtual void execute() = 0;
    };
    
    class ConcreteStrategyA : public Strategy {
    public:
      void execute() override {
        // Algorithm A implementation
      }
    };
    
    class ConcreteStrategyB : public Strategy {
    public:
      void execute() override {
        // Algorithm B implementation
      }
    };
    
    class Context {
    public:
      Context(Strategy* strategy) : strategy_(strategy) {}
    
      void setStrategy(Strategy* strategy) {
        strategy_ = strategy;
      }
    
      void executeStrategy() {
        strategy_->execute();
      }
    
    private:
      Strategy* strategy_;
    };
    Code language: C++ (cpp)

    In this example, the Strategy class represents the interface for all algorithms. The ConcreteStrategyA and ConcreteStrategyB classes represent concrete implementations of the Strategy class for two different algorithms.

    The Context class represents the client that uses the algorithms. It has a setStrategy() method to set the current algorithm and an executeStrategy() method to execute the current algorithm.

    Using the Strategy pattern allows us to change the behavior of a system at runtime by simply changing the current algorithm in the Context object. Conscious use of this pattern promotes code reuse, flexibility, and maintainability.

    State Behavioral Pattern

    I saved my favorite for last!

    The State design pattern is a behavioral pattern that allows an object to alter its behavior when its internal state changes. This pattern is useful when an object’s behavior depends on its state, and that behavior must change dynamically at runtime depending on the state.

    Essentially this boils down to defining a State base class that defines the basic structure for state information and defines the common interface, such as entry(), do(), and exit() methods.

    UML describing the State Behavioral Pattern

    C++ Example of the State Behavioral Pattern

    In C++, we can implement the State pattern using inheritance and polymorphism. We create a State base class that represents the interface for all states. Then, we create concrete implementations of the State class for each possible state of the object. Finally, we define a Context class that acts as the context in which the state machine operates. Clients utilize the interface in the Context class to manipulate the state machine.

    Here’s an example:

    class State {
    public:
        virtual void entry() = 0;
        virtual void do() = 0;
        virtual void exit() = 0;
    };
    
    class ConcreteStateA : public State {
    public:
        void entry() override {
            // Entry behavior for state A
        }
        void do() override {
            // State behavior for state A
        }
        void exit() override {
            // Exit behavior for state A
        }
    };
    
    class ConcreteStateB : public State {
    public:
        void entry() override {
            // Entry behavior for state B
        }
        void do() override {
            // State behavior for state B
        }
        void exit() override {
            // Exit behavior for state B
        }
    };
    
    class Context {
    public:
        Context(State* state) : state_(state) {}
    
        void transitionTo(State* state) {
            state_->exit();
            state_ = state;
            state_->entry();
        }
    
        void request() {
            state_->do();
        }
    
    private:
        State* state_;
    };
    Code language: C++ (cpp)

    In this example, the State class represents the interface for all states. The ConcreteStateA and ConcreteStateB classes represent concrete implementations of the State class for two different states.

    The Context class represents the object whose behavior depends on its internal state. It has a transitionTo() method to set the current state and a request() method to trigger a behavior that depends on the current state.

    You can also make use of templates in the Context class to define an addState() function. In this manner, you can enforce transitions to only a specific set of State classes and utilize custom lambda functions for the entry(), do(), and exit() functions for each state.

    Using the State pattern allows us to change the behavior of an object at runtime by simply changing its internal state. This makes our code more flexible and easier to maintain. It also promotes code reuse, as we can easily add new states by implementing new State classes.

    Conclusion

    In conclusion, design patterns are a powerful tool in software development that can help us solve common problems and improve our code’s flexibility, maintainability, and scalability. In this post, we have explored several design patterns in C++, including the Singleton, Abstract Factory, Adapter, Decorator, Facade, Mediator, Strategy, and State patterns.

    While each pattern has its unique characteristics and use cases, they all share the same goal: to provide a well-structured, reusable, and extensible solution to common software development problems. By understanding and using these patterns, we can write more efficient, robust, and maintainable code that can be easily adapted to changing requirements.

    As a conscious software developer, it’s essential to keep learning and improving our skills by exploring new ideas and concepts. Design patterns are an excellent place to start, as they can provide us with a deeper understanding of software architecture and design principles. By mastering design patterns, we can become more efficient and effective developers who can deliver high-quality, scalable, and maintainable software solutions.

    Additional Resources

    Here are a few additional resources for diving deeper into design patterns for software development.

    1. “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. This book is considered the definitive guide to design patterns and is a must-read for anyone interested in the subject.
    2. “Head First Design Patterns” by Eric Freeman, Elisabeth Robson, Bert Bates, and Kathy Sierra. This book offers a more accessible and engaging approach to learning design patterns and is ideal for beginners.
    3. Design Patterns in Modern C++ – This Udemy course covers the basics of design patterns and shows how to apply them using modern C++ programming techniques.
    4. C++ Design Patterns – This GitHub repository contains a collection of code examples for various design patterns in C++.
    5. Refactoring Guru – This website provides an extensive catalog of design patterns with code examples in multiple programming languages, including C++.
    6. Software Engineering Design Patterns – This Coursera course covers the principles and applications of design patterns in software engineering, including C++ examples.
  • Mastering Variable Scopes in C/C++: Best Practices for Clean and Effective Code

    As software developers, we rely on variables to store and manipulate data in our programs. However, it is crucial to understand the scope of a variable and how it affects its accessibility and lifetime. In C and C++, the scope of a variable determines where in the program it can be used and for how long it will exist. In this blog post, we will be exploring the different types of scopes in C/C++ and the best practices for handling them to write clean, maintainable, and effective code.

    We will look at global, local, and member scopes and how they affect the lifetime of variables. We will also discuss how to properly handle pointers, which have their own unique set of considerations when it comes to scope. By understanding the different types of scopes and how to handle them, you will be equipped to make conscious decisions about how you use variables in your code, leading to more reliable, efficient, and maintainable programs.

    Variable Scope Awareness

    Awareness of variable lifetimes and scopes, particularly when it comes to pointers, is critical to writing clean and effective C/C++ code. The lifetime of a variable is the period of time during which it is allocated memory and exists in the program. In C/C++, variables can have three different scopes: global scope, local scope, and member scope.

    Global Scope Variables

    Global scope variables are declared outside of all functions and are accessible throughout the entire program. They have a longer lifetime and persist throughout the execution of the program, but using too many global scope variables can lead to cluttered code and potential naming conflicts. However, in my mind, the more serious implications of improper use of a global variable is race conditions.

    A race condition occurs when two or more threads access a shared resource, such as a global variable, simultaneously and the final result depends on the timing of the access. In a safety critical environment, where errors in the system can have severe consequences, race conditions can cause significant harm.

    // Example of a global variable, including a potential race condition
    int32_t g_temperature_C = 0;
    
    void thread1(void)
    {
      // Read the temperature from the sensor
      g_temperature_C = ReadTemperatureFromSensor();
    }
    
    void thread2(void)
    {
      if ((g_temperature_C > 0) && (g_temperature_C < 70)) // !! Simple race condition
      {
        // Do some safety critical work
      }
      else
      {
        // Manage temperature out of bounds (i.e., cool down or heat up)
      }
    }
    Code language: C++ (cpp)

    In the example above, thread2 is doing some safety critical work, but only when g_temperature_C is within a certain range, which is updated in thread1. If the temperature is out of bounds, then the system needs to take a different action. The issue here is that the wrong action can lead to serious consequences, either for the safety of the system, or in the case where humans are involved, the safety of the user.

    In this case, a global variable is a poor choice of scope for g_temperature_C.

    If you find you do have to use global variables, you can still limit their scope to the specific compilation unit where they are defined (i.e., the file where the variable is declared). You can do this by adding the static keyword to the variable declaration. The advantage to this is that it limits the scope of the variable to just the specific module, rather than the entire program.

    // Limit scope of global variable to the specific compilation unit (i.e., this file)
    static int32_t g_temperature_C = 0;
    Code language: C++ (cpp)

    Local Scope Variables

    Local scope variables, on the other hand, are declared within a function or block and are only accessible within that specific scope. They have a shorter lifetime, are allocated on the stack, and are automatically deallocated from memory once the function or block has finished execution. Using local scope variables is recommended over global variables as they limit the potential for naming conflicts, allow for cleaner code, and also eliminate race conditions.

    // Example of a local variable, resolving the race condition above
    void thread2(void)
    {
      int32_t l_temperature_C = ReadTemperatureFromSensor();
      if ((l_temperature_C > 0) && (l_temperature_C < 70)) // !! NO race condition
      {
        // Do some safety critical work
      }
      else
      {
        // Manage temperature out of bounds (i.e., cool down or heat up)
      }
    }
    Code language: C++ (cpp)

    As you can see, the race condition from using a global variable is avoided here because the variable is local and cannot be changed outside of this function.

    Member Scope Variables

    Member scope variables, also known as class member variables, are declared within a class and are accessible by all member functions of that class. Their scope is tied to the lifetime of the object they are a member of.

    #include <iostream>
    
    class TemperatureSensor
    {
    public:
      TemperatureSensor() = default;
    
      void GetTemperature()
      {
        m_temp_C = ReadTemperatureFromSensor();
        return m_temp_C;
      }
    
    private:
      int32_t m_temp_C{0};
    };
    
    int main()
    {
      TemperatureSensor sensor;
      std::cout << "Temperature: " << sensor.GetTemperature() << std::endl;
      std::cout << "Temperature: " << sensor.GetTemperature() << std::endl;
    
      return 0;
    }Code language: PHP (php)

    You can think of the scope of member variables to be similar to that of static global variables. Instead of being limited to the compilation unit where they are declared, they are limited to the scope of the class that they are part of. Race conditions on member variables are a real possibility. Precautions must be taken to ensure you avoid them, such as proper locking or an improved architecture to avoid the race altogether.

    Properly Scoping Pointers

    Pointers are a powerful tool in C and C++, allowing you to efficiently work with data objects in your programs. However, naive usage of pointers can lead to significant problems, including hard to find bugs and difficult to maintain code.

    In C and C++, pointers have their own lifetime, separate from the objects they point to. When a pointer goes out of scope, the object it referenced remains in memory but is no longer accessible. When dynamically allocating memory, this leads to memory leaks where the memory is not properly deallocated, leading to a buildup of memory usage over time.

    Smart Pointers

    To prevent memory leaks and ensure that your programs are efficient and reliable, it is important to handle pointers with care. Modern C++ provides smart pointers types, which automatically manage the lifetime of objects and deallocate them when they are no longer needed. Using smart pointer types of std::shared_ptr and std::unique_ptr, you can be assured that when you create (and allocate) a pointer to an object, that object is constructed (and initialized if following RAII principles) and the pointer is valid. Then, when that pointer goes out of scope, the object is destructed and the memory is deallocated.

    #include <memory>
    #include <iostream>
    
    void PrintTemperature()
    {
      // Create a unique pointer to a TemperatureSensor object
      std::unique_ptr<TemperatureSensor> pTS = std::make_unique<TemperatureSensor>();
      
      // Use the unique pointer within the scope of the current function
      std::cout << "Temperature: " << pTS->GetTemperature() << std::endl;
      
      // The unique pointer goes out of scope at the end of the main function
      // and its dynamically allocated memory is automatically deallocated
    
    }Code language: PHP (php)

    When working with raw pointers, it’s critical to be aware of the lifetime of the objects being pointed to. For example, if the lifetime of the object ends before the pointer is deallocated, the pointer becomes a “dangling pointer”. This can cause undefined behavior, such as crashing the program or returning incorrect results. Smart pointers are typically a better choice and avoid this risk by managing the lifetime of the object themselves.


    In conclusion, understanding and properly handling the scope of variables in C/C++ is a crucial aspect of writing clean, maintainable, and effective code. By becoming familiar with global, local, and member scopes, and considering the lifetime and accessibility of variables, you can make informed decisions about how to use variables in your programs.

    Additionally, pointers require their own set of considerations when it comes to scope, and it is essential to handle them with care to prevent memory leaks and other issues.

    By following best practices and being aware of the potential pitfalls, you can ensure that your programs are reliable, efficient, and easy to maintain.

  • Getting Started with CMake: A Beginner’s Guide to Building Your Project

    CMake is an open-source, cross-platform build system that helps developers to manage their projects and build them on different platforms. It is widely used in the software development community, especially for C and C++ projects. In this blog post, we will explore how to use CMake effectively to manage your projects and improve your workflow as a software developer.

    An Example CMakeLists.txt

    First, let’s start with the basics of CMake. CMake uses a simple, human-readable language called CMakeLists.txt to describe the build process of a project. This file contains instructions on how to find and configure dependencies, set compiler flags, and create the final executable or library. Here is an example of how I typically define my CMake from my open-source ZeroMQ-based RPC library.

    ###############################################################################
    # CMakeLists.txt for zRPC library
    #  - Creates a CMake target library named 'zRPC'
    ###############################################################################
    cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
    
    # Define the project, including its name, version, and a brief description
    project(zRPC
            VERSION "0.0.1"
            DESCRIPTION "0MQ-based RPC client/server library with MessagePack support"
           )
    
    # Define CMake options to control what targets are generated and made available to build
    option(ZRPC_BUILD_TESTS "Enable build of unit test applications" ON)
    
    # Setup default compiler flags
    set(CMAKE_C_STANDARD 11)
    set(CMAKE_C_STANDARD_REQUIRED ON)
    set(CMAKE_CXX_STANDARD 20)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    set(compile_options -pedantic-errors
                        -pedantic
                        -Wall
                        -Wextra
                        -Wconversion
                        -Wsign-conversion
                        -Wno-psabi
                        -Werror
        CACHE INTERNAL "Compiler Options"
       )
    
    ###############################################################################
    # Bring in CPM
    ###############################################################################
    include(cmake/CPM.cmake)
    
    ###############################################################################
    # Bring in CPPZMQ header-only API
    ###############################################################################
    CPMAddPackage(
      NAME cppzmq
      VERSION 4.8.1
      GITHUB_REPOSITORY "zeromq/cppzmq"
      OPTIONS "CPPZMQ_BUILD_TESTS OFF"
    )
    
    ###############################################################################
    # Bring in MSGPACK-C header-only API
    ###############################################################################
    CPMAddPackage(
      NAME msgpack
      GIT_TAG cpp-4.1.1
      GITHUB_REPOSITORY "msgpack/msgpack-c"
      OPTIONS "MSGPACK_BUILD_DOCS OFF" "MSGPACK_CXX20 ON" "MSGPACK_USE_BOOST OFF"
    )
    
    ###############################################################################
    # Bring in C++ CRC header-only API
    ###############################################################################
    CPMAddPackage(
      NAME CRCpp
      GIT_TAG release-1.1.0.0
      GITHUB_REPOSITORY "d-bahr/CRCpp"
    )
    if(CRCpp_ADDED)
      add_library(CRCpp INTERFACE)
      target_include_directories(CRCpp SYSTEM INTERFACE ${CRCpp_SOURCE_DIR}/inc)
    endif(CRCpp_ADDED)
    
    ###############################################################################
    # zRPC library
    ###############################################################################
    add_library(${PROJECT_NAME} SHARED)
    target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
    target_link_libraries(${PROJECT_NAME} PUBLIC cppzmq msgpackc-cxx CRCpp pthread)
    target_sources(${PROJECT_NAME} PRIVATE  src/zRPCClient.cpp
                                            src/zRPCServer.cpp
                                            src/zRPCPublisher.cpp
                                            src/zRPCSubscriber.cpp
                                   PUBLIC   include/zRPC.hpp
                  )
    target_compile_options(${PROJECT_NAME} PUBLIC ${compile_options})
    
    
    ###############################################################################
    # Test applications
    ###############################################################################
    if (ZRPC_BUILD_TESTS)
      add_executable(client tests/client.cpp)
      target_link_libraries(client zRPC)
      target_compile_options(client PUBLIC ${compile_options})
    
      add_executable(server tests/server.cpp)
      target_link_libraries(server zRPC)
      target_compile_options(server PUBLIC ${compile_options})
    
      add_executable(publisher tests/publisher.cpp)
      target_link_libraries(publisher zRPC)
      target_compile_options(publisher PUBLIC ${compile_options})
    
      add_executable(subscriber tests/subscriber.cpp)
      target_link_libraries(subscriber zRPC)
      target_compile_options(subscriber PUBLIC ${compile_options})
    
      include(cmake/CodeCoverage.cmake)
      append_coverage_compiler_flags()
      add_executable(unittest tests/unit.cpp)
      target_link_libraries(unittest zRPC)
      target_compile_options(unittest PUBLIC ${compile_options})
    
      setup_target_for_coverage_gcovr_xml(NAME ${PROJECT_NAME}_coverage
                                          EXECUTABLE unittest
                                          DEPENDENCIES unittest
                                          BASE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                                          EXCLUDE "tests"
    				                             )
    endif(ZRPC_BUILD_TESTS)
    Code language: CMake (cmake)

    Once you have your CMakeLists.txt file created, you can use the CMake command-line tool to generate the build files for a specific platform, such as Makefiles or Visual Studio project files. It is considered best practice to keep your build files separated from your source files, so I am in the habit of creating a “_bld” directory for that purpose.

    mkdir _bld; cd _bld
    cmake ..

    CMake Targets

    Targets are the basic building blocks of a CMake project. They represent the executable or library that is built as part of the project. Each target has a unique name and is associated with a set of source files, include directories, and libraries that are used to build it.

    CMake also supports creating custom targets, which can be used to run arbitrary commands as part of the build process, such as running tests or generating documentation. You can specify properties for the target, like include directories, libraries, or compile options. You can also specify dependencies between the targets, so that when one target is built, it will automatically build any targets it depends on.

    This is a really powerful feature that CMake provides because when I define my library target, I define what it needs to build such as the source files, includes, and external libraries. Then, when I define my executable, I only need to specify the library that it depends on — the requisite includes and other libraries that need to be linked in come automatically!

    add_library(${PROJECT_NAME} SHARED)
    target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
    target_link_libraries(${PROJECT_NAME} PUBLIC cppzmq msgpackc-cxx CRCpp pthread)
    target_sources(${PROJECT_NAME} PRIVATE  src/zRPCClient.cpp
                                            src/zRPCServer.cpp
                                            src/zRPCPublisher.cpp
                                            src/zRPCSubscriber.cpp
                                   PUBLIC   include/zRPC.hpp
                  )
    target_compile_options(${PROJECT_NAME} PUBLIC ${compile_options})
    
    # The executable only needs to depend on zRPC now,
    # not all the other dependencies and include directories
    add_executable(client tests/client.cpp)
    target_link_libraries(client zRPC)Code language: PHP (php)

    Dependency Management

    One of the most important aspects of CMake is its ability to help you find and use dependencies. CMake provides a number of built-in commands that can be used to find and configure dependencies, such as find_package and find_library. These commands can be used to locate and configure external libraries, such as Boost or OpenCV, and make them available to your project. This can save you a lot of time and effort compared to manually configuring dependencies for each platform, which is how it was done with plain Makefiles in the past.

    In my example above, I use a tool called CPM, or the CMake Package Manager. This is an abstraction of the find_package and find_library methods available in the CMake language. One huge advantage of this tool is that it can not only be used to find and use local packages, but it can be used to pull packages at a specific version or tag from remote git repositories. You can see how I used this to pull in the cppzmq, msgpack, and CRCpp packages that my library depends on.

    ###############################################################################
    # Bring in CPM
    ###############################################################################
    include(cmake/CPM.cmake)
    
    ###############################################################################
    # Bring in CPPZMQ header-only API
    ###############################################################################
    CPMAddPackage(
      NAME cppzmq
      VERSION 4.8.1
      GITHUB_REPOSITORY "zeromq/cppzmq"
      OPTIONS "CPPZMQ_BUILD_TESTS OFF"
    )
    
    ###############################################################################
    # Bring in MSGPACK-C header-only API
    ###############################################################################
    CPMAddPackage(
      NAME msgpack
      GIT_TAG cpp-4.1.1
      GITHUB_REPOSITORY "msgpack/msgpack-c"
      OPTIONS "MSGPACK_BUILD_DOCS OFF" "MSGPACK_CXX20 ON" "MSGPACK_USE_BOOST OFF"
    )
    
    ###############################################################################
    # Bring in C++ CRC header-only API
    ###############################################################################
    CPMAddPackage(
      NAME CRCpp
      GIT_TAG release-1.1.0.0
      GITHUB_REPOSITORY "d-bahr/CRCpp"
    )
    if(CRCpp_ADDED)
      add_library(CRCpp INTERFACE)
      target_include_directories(CRCpp SYSTEM INTERFACE ${CRCpp_SOURCE_DIR}/inc)
    endif(CRCpp_ADDED)
    Code language: CMake (cmake)

    Cross-Platform Build Support

    Another powerful feature of CMake is its ability to generate build files for multiple platforms. For example, you can use CMake to generate Makefiles for Linux, Visual Studio project files for Windows, or Xcode project files for macOS. This allows you to easily build and test your project on different platforms, without having to manually configure the build process for each one.

    # Basic command to generate Makefiles (Linux/MacOS)
    cmake -G "Unix Makefiles" ..
    
    # Basic command to generate Visual Studio build files
    cmake -G "Visual Studio 16" -A x64 ..
    
    # More complex command from the VS Code CMake extension performing cross-compilation for ARM
    /usr/bin/cmake --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE \
        -DCMAKE_BUILD_TYPE:STRING=Release 
        -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/arm-linux-gnueabihf-gcc-10 
        -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/arm-linux-gnueabihf-g++-10 
        -DARCH:STRING=armv7 -DENABLE_TESTS:STRING=ON 
        -S/workspaces/zRPC -B/workspaces/zRPC/_bld/ARM/Release 
        -G "Unix Makefiles"Code language: PHP (php)

    Best Practices

    To improve your workflow with CMake, there are a few best practices that you should follow:

    • Keep your CMakeLists.txt files small and organized. The build process of a project can become complex, so it’s important to keep your CMakeLists.txt files well-organized and easy to understand.
    • Use variables to define common build options, such as compiler flags or library paths. This makes it easy to change these options globally, without having to modify multiple parts of your CMakeLists.txt files.
    • Use include() and add_subdirectory() commands to split your project into smaller, more manageable parts. This makes it easier to understand the build process, and also makes it easy to reuse parts of your project in other projects. I have found that many, small CMake files are easier to manage and maintain than fewer, large CMake files.
    • Use the install() command to specify where the final executable or library should be installed. This makes it easy to distribute your project to other users.
    • Use the add_custom_command() and add_custom_target() commands to add custom build steps to your project. For example, you can use these commands to run a script that generates source code files or to run a test suite after building.

    By following these best practices, you can effectively use CMake to manage your projects and improve your workflow as a software developer. CMake is a powerful tool that can save you a lot of time and effort, and by mastering its features, you can build and distribute your projects with ease.