Blog

  • Proxy Design Pattern in C++

    Proxy Design Pattern is a Structural Design Pattern that allows us to provide a substitute or we can say a placeholder for another object. In Proxy Design Pattern, a class represents the functionality of another class. A proxy receives client requests, does some work (access controlcaching, etc.) and then passes the request to a service object (the original object).

    Proxy Design Pattern is used when we need to create a wrapper for an object to control access to it. It is also used to add additional functionality to an object without changing its code.

    Take the following example. In the image, the man at the gate talks to visitors for the queen because she’s busy. He passes messages and only lets the right ones in — kind of like a helper who speaks for her. That’s what a proxy does.

    Proxy Design Pattern

    Types of Proxy Design Pattern

    Following are the different types of Proxy Design Pattern −

    • Remote Proxy − Lets you use something that’s far away, maybe on another computer.
    • Virtual Proxy − Creates the real thing only when you actually need it, so it saves time and memory.
    • Protection Proxy − Checks if you’re allowed to use something before giving you access.
    • Caching Proxy − Stores results and gives them back quickly if you ask for the same thing again.
    • Logging Proxy − Keeps a record of what happens when you use the real object.
    • Synchronization Proxy − Makes sure only one person can use something at a time to avoid clashes.
    • Smart Reference Proxy − Adds a few extra steps when you use something, like counting how many times it’s used or loading it only when needed.

    Components of Proxy Design Pattern

    • Subject − The basic rules or actions that both the real object and the helper use.
    • RealSubject − The real object that does the main job.
    • Proxy − The helper that talks for the real object and controls who can use it.

    Implementation of Proxy Design Pattern in C++

    We will implement a simple example of Proxy Design Pattern in C++. In this example, we will create a proxy for a Database class that simulates a connection to a database. The proxy will control access to the database and add caching functionality.

    Database Proxy Example

    Steps to Implement Proxy Design Pattern

    Follow the steps below to implement the Proxy Design Pattern in C++

    • Define the Subject interface that declares the common operations for both the real object and the proxy.
    • Create the RealSubject class that implements the Subject interface and simulates a database connection.
    • Create the Proxy class that also implements the Subject interface and controls access to the RealSubject. It will add caching functionality.
    • Use the Proxy class in the client code to access the database.

    C++ Code for Proxy Design Pattern

    Below is the C++ code that demonstrates the Proxy Design Pattern −

    Here, the DatabaseProxy class acts as a middleman for the RealDatabase class. It saves the result of the first query and gives back the saved result if you ask again, showing how a proxy can cache data.

    #include <iostream>#include <string>usingnamespace std;// Subject InterfaceclassDatabase{public:virtual string query(const string& sql)=0;virtual~Database(){}};// RealSubject ClassclassRealDatabase:public Database{public:
       string query(const string& sql)override{// Simulate a database queryreturn"Results for query: "+ sql;}};// Proxy ClassclassDatabaseProxy:public Database{private:
       RealDatabase* realDatabase;
       string cachedResult;bool isCached;public:DatabaseProxy():realDatabase(nullptr),isCached(false){}
       string query(const string& sql)override{if(!isCached){if(realDatabase ==nullptr){
                realDatabase =newRealDatabase();}
             cachedResult = realDatabase->query(sql);
             isCached =true;}else{
             cout <<"Returning cached result."<< std::endl;}return cachedResult;}~DatabaseProxy(){delete realDatabase;}};// Client Codeintmain(){
       Database* db =newDatabaseProxy();// First query - will access the real database
       cout << db->query("SELECT * FROM users")<< endl;// Second query - will return cached result
       cout << db->query("SELECT * FROM users")<< endl;delete db;return0;}

    Following is the output of the above code −

    Results for query: SELECT * FROM users
    Returning cached result.
    Results for query: SELECT * FROM users
    

    Pros and Cons of Proxy Design Pattern

    Following are the advantages and disadvantages of using the Proxy Design Pattern −

    ProsCons
    Lets you control who can use something.Adds an extra step, which can make things more complicated.
    You can add things like saving results or keeping records without changing the main object.Can slow things down because of the extra work the proxy does.
    Helps you create things only when you really need them.Can make fixing problems harder because there’s more to check.
    Makes things safer by checking who can use them.Not always needed, and can get messy if the proxy does too much of work.

    When to Use Proxy Design Pattern?

    Following are some scenarios where you might want to use the Proxy Design Pattern −

    • Control who can use something.
    • Add saving results or keeping records without changing the main object.
    • Delay making the real thing until it is needed.
    • Keep something safe from people who shouldn’t use it.
    • Manage how and when something is used.

    Real-World Applications of Proxy Design Pattern

    Following are some real-world applications of the Proxy Design Pattern −

    Real World Proxy Examples

    Conclusion

    In this chapter, you learned what the Proxy Design Pattern is, how it works, and where you can use it. Proxy helps you control access to something and lets you add new features without changing the main object. It’s a handy way to keep things safe and organized.

  • Command Design Pattern in C++

    The Command Design Pattern is a way to turn a request—like asking a light to turn on—into its own object. This means you can treat actions as things you can pass around, save for later, or even undo if you want.

    In simple terms, it lets you wrap up an action and all the details it needs, so you can run it whenever you like. This makes your code more flexible and easier to manage, especially when you want to add features like undo or keep a history of what happened.

    For example, in a text editor, actions like CopyPaste, and Undo can each be their own command object. This makes it easy to run them, line them up, or take them back if you change your mind.

    In the following image, you can see a remote control sending commands to a light source. The remote control (Invoker) doesn’t know how the Light (Receiver) works; it just tells it to turn on or off using Command objects.

    Command Design Pattern

    Components of Command Design Pattern

    Following are the main components of the Command Design Pattern −

    • Command − This is just an interface or abstract class with a method like execute(). It’s a common way to say, “Here’s what a command should look like.”
    • ConcreteCommand − These are the actual command classes. Each one does something specific, like turning a light on or off. They know which object to work with and what action to perform.
    • Client − This is the part of your code that puts everything together. It creates the command objects and tells them which things they should control.
    • Invoker − Think of this as the remote control. It doesn’t know what the command does, it just knows how to ask the command to do its job.
    • Receiver − This is the real worker. It’s the object that actually does the work when a command is executed, like the light that turns on or off.

    C++ Implementation of Command Design Pattern

    In this we will implement a simple command pattern example where we have a light that can be turned on and off using command objects.

    Steps to Implement Command Design Pattern in C++

    Following are the steps to implement Command Design Pattern in C++

    Steps to Implement Command Design Pattern
    • First, make an interface (or abstract class) for your commands, with a method like execute().
    • Next, create classes for each command (like turning a light on or off) that implement this interface.
    • Then, make a class for the thing you want to control (for example, a Light class that knows how to turn itself on or off).
    • After that, create an “invoker” class (like a remote control) that will use the command objects to do the work.
    • Finally, in your main code, put everything together: make the commands, connect them to the thing they control, and use the invoker to run them.

    C++ Code Example of Command Design Pattern

    Following is a simple C++ code example demonstrating the Command Design Pattern −

    #include <iostream>usingnamespace std;// Command InterfaceclassCommand{public:virtualvoidexecute()=0;};// Receiver ClassclassLight{public:voidon(){
           cout <<"Light is ON"<< endl;}voidoff(){
           cout <<"Light is OFF"<< endl;}};// Concrete Command to turn on the lightclassLightOnCommand:public Command{private:
       Light* light;public:LightOnCommand(Light* l):light(l){}voidexecute()override{
             light->on();}};// Concrete Command to turn off the lightclassLightOffCommand:public Command{private:
       Light* light;public:LightOffCommand(Light* l):light(l){}voidexecute()override{
             light->off();}};// Invoker ClassclassRemoteControl{private:
       Command* command;public:voidsetCommand(Command* cmd){
          command = cmd;}voidpressButton(){
          command->execute();}};// Client Codeintmain(){
       Light* light =newLight();
    
       Command* lightOn =newLightOnCommand(light);
       Command* lightOff =newLightOffCommand(light);
    
       RemoteControl* remote =newRemoteControl();// Turn the light ON
       remote->setCommand(lightOn);
       remote->pressButton();// Turn the light OFF
       remote->setCommand(lightOff);
       remote->pressButton();// Clean updelete light;delete lightOn;delete lightOff;delete remote;return0;}

    Following is the output of the above code −

    Light is ON
    Light is OFF
    

    In this example, we have defined a Command interface with an execute method. The Light class acts as the Receiver that knows how to turn the light on and off. The LightOnCommand and LightOffCommand classes are ConcreteCommand classes that implement the Command interface. The RemoteControl class is the Invoker that calls the command to execute the request. Finally, in the main function, we create instances of the commands and use the RemoteControl to execute them.

    Pros and Cons of Command Design Pattern

    ProsCons
    Makes it easy to separate who sends a request from who actually does the workYou might end up with a lot of command classes
    Lets you add undo and redo featuresMakes the code a bit more complicated
    You can queue up or log requests for laterNeeds extra classes to work
    Lets you give different requests to the same clientCan feel like too much for simple tasks
    Keeps your code tidy and easier to manageCan make finding bugs a bit harder
    Enhances flexibility in executing commandsPotential performance overhead

    Real-World Use Cases of Command Design Pattern

    • In graphical user interfaces, handling actions like clicking buttons or selecting menu items.
    • Adding undo and redo features in programs such as text editors or drawing tools.
    • Creating systems that schedule tasks to run later, like job queues.
    • Controlling smart home devices with a remote or app.
    • Recording and playing back a series of actions as macros in software.
    Real World Application of Command Design Pattern

    Conclusion

    In this chapter, we learned what is commnand design pattern, its components, and how to implement it in C++. We also discussed the pros and cons of using this pattern along with real-world use cases. The Command Design Pattern is a powerful tool for decoupling the sender and receiver of requests, it enables features like undo/redo, and improves code organization.

  • Decorator Design Pattern in C++

    decorator is a Structural Design Pattern that allows us to add new functionalities to an existing object without changing or disturbing its structure. It lets us attach new behaviors to an object by placing that object inside another special wrapper object that contains the new behavior.

    Let’s understand in detail with an example. Imagine you have a simple text editor application with basic text features like writing and saving or editing text. Now, your manager wants you to add some new features like spell checking and grammar checking. If you are using the Decorator Design Pattern, you can easily add these new features without changing the existing code of the text editor. You can create separate decorator classes for spell checking and grammar checking that wrap around the original text editor class.

    Decorator Design Pattern Example

    Components of Decorator Design Pattern

    There are four main components of Decorator Design Pattern, as shown in the following image. Also, the client is the one who uses these components to perform operations. We have added two

    Components of Decorator Pattern

    Let’s understand each of these components in detail −

    • Component − This is an interface or abstract class that defines the common methods for both the concrete component and the decorators. In our example, this would be the interface for the text editor.
    • Concrete Component − This is the class that implements the component interface and represents the original object that we want to add new functionalities to. In our example, this would be the basic text editor class.
    • Decorator − This is an abstract class that also implements the component interface and has a reference to a component object. The decorator class is responsible for adding new functionalities to the component object. In our example, this would be the abstract decorator class for the text editor.
    • Concrete Decorators − These are the classes that extend the decorator class and implement the new functionalities. In our example, these would be the spell checker and grammar checker classes that add their respective functionalities to the text editor.

    Implementation of Decorator Design Pattern in C++

    Earlier, we discussed the components of the Decorator Design Pattern. Now, let’s see how we can implement it in C++.

    In this implementation, we will create a simple Music Player application where we have a basic music player that can play music. We will then create decorators to add new functionalities like Shuffle and Repeat to the music player.

    Music Player Example Decorator Design Pattern

    Steps to Implement Decorator Design Pattern

    Let’s see how we can implement the Decorator Design Pattern in C++

    • Create a Component interface for the music player.
    • Implement a Concrete Component class for the basic music player.
    • Create an abstract Decorator class that implements the Component interface.
    • Implement two concrete Decorator classes for shuffle and repeat functionalities.
    • Create a Client class to use the music player with decorators.

    C++ Code for Decorator Design Pattern

    Let’s look at the C++ code for the Decorator Design Pattern implementation of a Music Player application.

    Here we will use all the components of the Decorator Design Pattern that we discussed earlier. We will create a Component interface for the music player, a Concrete Component class for the basic music player, an abstract Decorator class that implements the Component interface, and concrete Decorator classes for shuffle and repeat functionalities.

    #include <iostream>usingnamespace std;// ComponentclassMusicPlayer{public:virtualvoidplay()=0;virtual~MusicPlayer(){}};// Concrete ComponentclassBasicMusicPlayer:public MusicPlayer{public:voidplay(){
          cout <<"Playing music..."<< endl;}};// DecoratorclassMusicPlayerDecorator:public MusicPlayer{protected:
       MusicPlayer* player;public:MusicPlayerDecorator(MusicPlayer* p):player(p){}virtualvoidplay(){
          player->play();}virtual~MusicPlayerDecorator(){delete player;}};// Concrete Decorator for ShuffleclassShuffleDecorator:public MusicPlayerDecorator{public:ShuffleDecorator(MusicPlayer* p):MusicPlayerDecorator(p){}voidplay(){
          cout <<"Shuffling playlist..."<< endl;
          player->play();}};// Concrete Decorator for RepeatclassRepeatDecorator:public MusicPlayerDecorator{public:RepeatDecorator(MusicPlayer* p):MusicPlayerDecorator(p){}voidplay(){
          cout <<"Repeating current song..."<< endl;
          player->play();}};// Clientintmain(){
       MusicPlayer* player =newBasicMusicPlayer();
       MusicPlayer* shufflePlayer =newShuffleDecorator(player);
       MusicPlayer* repeatShufflePlayer =newRepeatDecorator(shufflePlayer);
    
       cout <<"Basic Music Player:"<< endl;
       player->play();
    
       cout <<"\nMusic Player with Shuffle:"<< endl;
       shufflePlayer->play();
    
       cout <<"\nMusic Player with Shuffle and Repeat:"<< endl;
       repeatShufflePlayer->play();delete repeatShufflePlayer;// This will also delete shufflePlayer and playerreturn0;}

    Following is the output of the above code −

    Basic Music Player:
    Playing music...
    
    Music Player with Shuffle:
    Shuffling playlist...
    Playing music...
    
    Music Player with Shuffle and Repeat:
    Repeating current song...
    Shuffling playlist...
    Playing music...
    

    Pros and Cons of Decorator Design Pattern

    The following table highlights the pros and cons of using the Decorator Design Pattern −

    ProsCons
    Allows adding new functionality without altering existing code.Can lead to a large number of small classes.
    Promotes code reusability by using composition over inheritance.Can make the code more complex and harder to understand.
    Stick to the Single Responsibility Principle by dividing functionalities into separate classes.Debugging can be more difficult due to the multiple layers of wrapping.
    Enhances flexibility by allowing dynamic addition of responsibilities.There could be performance overhead due to multiple layers of wrapping.
    Can be combined with other design patterns for more complex scenarios.May lead to issues with object identity and equality checks.

    When to Use Decorator Design Pattern?

    Following are some scenarios where you should consider using the Decorator Design Pattern −

    • To add more functionalities to an object dynamically.
    • When you want to avoid class explosion due to multiple combinations of features.
    • To stick to the Single Responsibility Principle by dividing functionalities into separate classes.
    • For adding responsibilities to individual objects without affecting other objects of the same class.
    • To enhance the behavior of an object at runtime.

    Real-world Applications of Decorator Design Pattern

    The following image illustration some of real-world applications of Decorator Design Pattern –

    Real World Applications Decorator Design Pattern

    Conclusion

    In this chapter, we learned about the Decorator Design Pattern, its components, implementation steps, and a C++ code example. We also discussed the pros and cons of using this pattern, when to use it, and some real-world applications. The Decorator Design Pattern is a powerful tool that allows us to add new functionalities to existing objects without changing their structure, which in turn makes it easy for us to maintain and extend our program codes.

  • Composite Design Pattern in C++

    Composite Design Pattern is a structural design pattern that allows you to compose objects into tree like structures that represent part-whole hierarchies. This pattern lets clients treat any individual object and a composition of objects uniformly.

    Sounds complicated? Let’s break it down with an example.

    • Suppose you are building a graphic design application which allows users to create and manipulate various shapes like circlesrectangles, and lines.
    • You also want to allow user to group the shapes together to form new designs(like a house made of rectangles and triangles, or a tree made of circles and lines).
    • With the Composite Design Pattern, you can treat both individual shapes and groups of shapes uniformly. This means that you can perform operations like moveresize, or draw on both single shapes and groups of shapes without worrying about their specific types.
    Composite Design Pattern Example

    Components of Composite Design Pattern

    The Composite Pattern is made up of multiple components. Take a look at the following image. Here, we can see the four main components of the Composite Design Pattern. The Component is an interface that has common methods for both Leaf and Composite. The Leaf is like a loner who doesn’t have any children, while the Composite is like a parent who can have multiple children (which can be either Leaf or other Composite). And, lastly we have the Client, which is like the boss who interacts with the Component interface to perform operations on both Leaf and Composite.

    Components of Composite Pattern

    Implementation of Composite Design Pattern in C++

    Now, that we understand the components of the Composite Design Pattern, let’s see how we can implement it in C++.

    In this implementation, we will implement a simple File System where we have Files and Directories. A File is a Leaf and a Directory is a Composite that can contain multiple Files and other Directories.

    File System Example Composite Design Pattern

    Steps to Implement Composite Design Pattern

    Let’s look at the steps to implement the File System using the Composite Design Pattern in C++:

    First, create a Component interface that has all the common methods. Next, create a Leaf class that implements the Component interface. Then, create a Composite class that also implements the Component interface and has a list to store its children. Finally, create a Client class that interacts with the Component interface to perform operations on both Leaf and Composite.

    Steps Composite Design Pattern

    The code in image is just for representation purpose. The complete code is given below.

    C++ Code for Composite Design Pattern

    In this code, we will define a FileSystemComponent interface that has a method showDetails(). The File class implements this interface and represents a leaf node in the file system. The Directory class also implements the FileSystemComponent interface and represents a composite node that can contain multiple children (both files and directories).

    Following is the complete C++ code for the Composite Design Pattern implementation of a File System −

    #include <iostream>#include <vector>#include <memory>usingnamespace std;// ComponentclassFileSystemComponent{public:virtualvoidshowDetails()=0;};// LeafclassFile:public FileSystemComponent{private:
       string name;public:File(string fileName):name(fileName){}voidshowDetails(){
          cout <<"File: "<< name << endl;}};//  Composite classDirectory:public FileSystemComponent{private:
       string name;
       vector<shared_ptr<FileSystemComponent>> children;public:Directory(string dirName):name(dirName){}voidadd(shared_ptr<FileSystemComponent> component){
          children.push_back(component);}voidshowDetails(){
          cout <<"Directory: "<< name << endl;for(auto child : children){
             child->showDetails();}}};// Clientintmain(){
       shared_ptr<FileSystemComponent> file1 =make_shared<File>("File1.txt");
       shared_ptr<FileSystemComponent> file2 =make_shared<File>("File2.txt");
       shared_ptr<FileSystemComponent> dir1 =make_shared<Directory>("Dir1");
       shared_ptr<FileSystemComponent> dir2 =make_shared<Directory>("Dir2");
    
       dir1->add(file1);
       dir2->add(file2);
       dir2->add(dir1);
    
       dir2->showDetails();return0;}

    Following is the output of the above code −

    Directory: Dir2
    File: File2.txt
    Directory: Dir1
    File: File1.txt
    

    Advantages and Disadvantages of Composite Design Pattern

    Following are the advantages and disadvantages of using the Composite Design Pattern −

    AdvantagesDisadvantages
    Makes code easier to use.Can make code harder to understand.
    Easy to add new parts.Slow if there are many parts.
    Helps reuse code.Can be tricky to test and fix bugs.
    Good for building big structures.Can make things too complicated.
    Easy to update code.Confusing to see how everything fits.
    Can add new things without changing old code.Uses more memory.

    When to Use Composite Design Pattern?

    You should consider using the Composite Design Pattern in the following scenarios −

    • When you have a part-whole hierarchy in your application.
    • To represent tree structures, such as file systems or organizational hierarchies.
    • For treating individual objects and compositions of objects uniformly.
    • While simplifying client code by allowing it to interact with a single interface.
    • To add new types of components without changing existing code.
    • When you want to manage complex structures more easily.
    • Performing operations on both individual objects and groups of objects.

    Real World Examples of Composite Design Pattern

    Following are some real-world examples of the Composite Design Pattern −

    Real World Applications Composite Design Pattern

    Conclusion

    In this chapter, we learned about the Composite Design Pattern, its components, implementation in C++, advantages, disadvantages, and when to use it. The Composite Design Pattern is a useful tool for managing complex structures and hierarchies in software development. By using this pattern, we can simplify our code and make it more easy to maintain and also extend in the future.

  • Bridge Design Pattern in C++

    Bridge Design Pattern is a structural design pattern which helps to separate the following two aspects of a software system −

    • Abstraction − Provides the interface for client code, hiding the implementation details.
    • Implementation − Provides the concrete implementation of the abstraction.

    By separating the above aspects, we can make both the abstraction and implementation independently expandable. This means that we can add new abstractions and implementations without affecting existing code.

    Bridge Design Pattern

    Think of it like a Coffee shop. You love coffee but alone it would be plain. Sometimes you want Espresso, sometimes Latte, and sometimes you want to add MilkSugar, or Honey.

    The coffee type (EspressoLatte) is the abstraction, and the add-ons (MilkSugarHoney) are the implementations. Instead of creating separate classes for every possible combination (like “EspressoWithMilk” or “LatteWithSugar“), the Bridge Pattern lets coffee delegate to add-ons through composition. This makes the system more flexible, that allows new coffee types or new add-ons to be added without changing existing code, and avoids the explosion of subclasses.

    Abstraction

    The Abtraction here is not same as in Abstract classes or Interfaces. It is a higher level of abstraction that separates the interface from the implementation.

    We only see “what” we need to see, instead of “how” it is implemented. The Abstraction contains a reference to the Implementor.

    Abstraction Simlified Interfaces

    Implementor

    The Implementor defines the interface for implementation classes. This interface doesn’t have to be exactly match to Abstraction’s interface; in fact, the two interfaces can be different. Typically, the Implementor interface provides only primitive operations, and Abstraction defines higher-level operations based on those primitives.

    We don’t care about “what” it does, we care about “how” it is done. The Implementor doesn’t contain any reference to the Abstraction.

    Implementor Concrete Implimentation

    Implementation of Bridge Design Pattern in C++

    Let’s implement the Bridge Design Pattern in C++ using the coffee shop example mentioned earlier. We’ll create an abstraction for Coffee and an implementor for AddOns.

    Steps to Implement Bridge Design Pattern

    Following image shows the steps to implement Bridge Design Pattern.

    Steps to Implement Bridge Design Pattern

    C++ Code for Bridge Design Pattern

    In this example, we have an abstract class AddOn that defines the interface for add-ons like MilkSugar, and Honey. The Coffee class is the abstraction that contains a reference to an AddOn. The concrete classes Espresso and Latte are refined abstractions that implement the serve() method.

    #include <iostream>usingnamespace std;// Implementor: AddOnclassAddOn{public:virtualvoidadd()=0;};// Concrete AddOn: MilkclassMilk:public AddOn{public:voidadd()override{
             cout <<"with Milk";}};// Concrete AddOn: SugarclassSugar:public AddOn{public:voidadd()override{
             cout <<"with Sugar";}};// Concrete AddOn: HoneyclassHoney:public AddOn{public:voidadd()override{
             cout <<"with Honey";}};// Abstraction: CoffeeclassCoffee{protected:// Bridge
         AddOn* addon;public:Coffee(AddOn* a):addon(a){}virtualvoidserve()=0;};// Refined Abstraction: EspressoclassEspresso:public Coffee{public:Espresso(AddOn* a):Coffee(a){}voidserve()override{
             cout <<"Espresso ";
             addon->add();
             cout << endl;}};// Refined Abstraction: LatteclassLatte:public Coffee{public:Latte(AddOn* a):Coffee(a){}voidserve()override{
             cout <<"Latte ";
             addon->add();
             cout << endl;}};// Clientintmain(){
       AddOn* milk =newMilk();
       AddOn* sugar =newSugar();// Espresso with Milk
       Coffee* espressoMilk =newEspresso(milk);// Latte with Sugar
       Coffee* latteSugar =newLatte(sugar);
    
       espressoMilk->serve();
       latteSugar->serve();delete milk;delete sugar;delete espressoMilk;delete latteSugar;}

    Following is the output of the above code −

    Espresso with Milk
    Latte with Sugar
    

    In this implementation, we can easily add new types of coffee or new add-ons without modifying existing code. For example, we can create a new class Mocha that extends Coffee or a new class Caramel that extends AddOn, and the existing classes will remain unaffected.

    When to Use Bridge Design Pattern

    • Change how things work without changing what they do − Use Bridge to swap or update implementations anytime.
    • Grow both sides easily − Add new features or new ways of doing things without breaking old code.
    • Keep code clean and easy to change − Bridge keeps things simple and ready for easy updates.
    • Handle lots of choices and combinations − Manage many types and options without confusion.
    • Avoid too many classes − Prevent a mess of classes for every mix—use composition instead.

    Real-world Software Use-cases of Bridge Design Pattern

    Following is a diagram that shows some real-world software use-cases of Bridge Design Pattern.

    Real-world Software Use-cases of Bridge Design Pattern

    Conclusion

    In this chapter, we learned about the Bridge Design Pattern, which is a structural design pattern that separates an abstraction from its implementation. We discussed the components of the pattern, including Abstraction and Implementor, and saw how they work together and provide way to extend both independently and maintain flexibility(composition over inheritance). We also implemented the Bridge Design Pattern in C++ using a coffee shop example, demonstrating how to create abstractions for Coffee and implementors for AddOns. Finally, we explored when to use the Bridge Design Pattern and some real-world software use-cases.

  • Adapter Design Pattern in C++

    Adapter is a part of Structural Design PatternsAdapter pattern works like a bridge between two incompatible interfaces. It involves a single class which is responsible for joining the functionalities of interfaces that are independent and not compatible with each other. This pattern is also known as Wrapper.

    Adapter works as a middleman of two things that do not go along together. For example, your laptop has a USB port and you have a SD card that you want to connect to your laptop. Here, USB and SD are two incompatible interfaces. To make them compatible, you need an adapter that can convert SD to USB. So, in the market you will find SD to USB adapter. This adapter will have SD port on one side and USB port on another side. You can connect your SD card to the adapter and then connect the adapter to your laptop’s USB port. Now, you can access your SD card data through your laptop.

    The Adapter pattern is used when you want to use some existing code, but its interface does not match the one you need. In such cases, you create an adapter class that implements the interface you want and translates calls to the existing code.

    Adapter Design Pattern

    In the above image, we have a Phone which does not have headphone jack. So, we cannot connect our headphones directly to the Phone. To solve this problem, we use an Adapter which has a lightning connector on one side and a headphone jack on another side. We can connect the adapter to the Phone and then connect our headphones to the adapter. This way, we can use our headphones with the Phone. This is an example of the Adapter pattern in real life.

    Types of Adapter Pattern

    There are two types of Adapter pattern −

    • Class Adapter Pattern − In this, we use multiple inheritance to adapt one interface to another.
    • Object Adapter Pattern − We use composition to adapt one interface to another.

    Let’s understand in detail how these two types of adapter patterns work.

    Class Adapter Pattern in C++

    We use Class Adapter Pattern when we want to adopt one interface to another using multiple inheritance. In this pattern, the adapter class inherits from both the target interface and the adaptee class. This way, the adapter can override methods from the target interface and use methods from the adaptee class to provide the functionality we want.

    Steps to implement Class Adapter Pattern are as follows −

    Class Adapter Pattern

    Example of Class Adapter Pattern in C++

    In this example, a modern music player wants us to play MP3 files, but we only have an old cassette player. Using the Class Adapter Pattern, we adapt the old cassette player so it can be used as an MP3 player.

    #include <iostream>usingnamespace std;// Target interfaceclassIMediaPlayer{public:virtualvoidplayMP3(string filename)=0;};// Adaptee classclassOldCassettePlayer{public:voidplayCassette(string tape){
             cout <<"Playing cassette tape: "<< tape << endl;}};// Adapter classclassCassetteToMP3Adapter:public IMediaPlayer, private OldCassettePlayer{public:voidplayMP3(string filename)override{// Convert MP3 filename to cassette tape format
             string tape = filename +".cassette";playCassette(tape);// Use the adaptee's method}};// Client codeintmain(){
       IMediaPlayer* player =newCassetteToMP3Adapter();
       player->playMP3("MyFavoriteSong");delete player;return0;}

    Following is the output of the above code −

    Playing cassette tape: MyFavoriteSong.cassette
    

    In this example, the IMediaPlayer interface is the target interface that expects an MP3 player. The OldCassettePlayer class is the adaptee that can only play cassette tapes. The CassetteToMP3Adapter class inherits from both the target interface and the adaptee class, allowing it to adapt the cassette player to work as an MP3 player.

    Object Adapter Pattern in C++

    We use Object Adapter Pattern when we want to adopt one interface to another using composition. In this pattern, the adapter class contains an instance of the adaptee class and implements the target interface. The adapter class translates calls from the target interface to the adaptee class.

    Steps to implement Object Adapter Pattern are as follows:

    Steps are almost same as Class Adapter Pattern except that in Object Adapter Pattern we use composition (has-a relationship) instead of inheritance (is-a relationship).

    Object Adapter Pattern

    Example of Object Adapter Pattern in C++

    Now, Let’s implement the same example of music player using Object Adapter Pattern.

    #include <iostream>usingnamespace std;// Target interfaceclassIMediaPlayer{public:virtualvoidplayMP3(string filename)=0;};// Adaptee classclassOldCassettePlayer{public:voidplayCassette(string tape){
             cout <<"Playing cassette tape: "<< tape << endl;}};// Adapter classclassCassetteToMP3Adapter:public IMediaPlayer{private:
          OldCassettePlayer* cassettePlayer;// Compositionpublic:CassetteToMP3Adapter(OldCassettePlayer* player){this->cassettePlayer = player;}voidplayMP3(string filename)override{// Convert MP3 filename to cassette tape format
             string tape = filename +".cassette";
             cassettePlayer->playCassette(tape);// Use the adaptee's method}};// Client codeintmain(){
       OldCassettePlayer* oldPlayer =newOldCassettePlayer();
       IMediaPlayer* player =newCassetteToMP3Adapter(oldPlayer);
       player->playMP3("MyFavoriteSong");delete player;delete oldPlayer;return0;}

    Following is the output of the above code −

    Playing cassette tape: MyFavoriteSong.cassette
    

    In this example, the IMediaPlayer interface is the target interface that expects an MP3 player. The OldCassettePlayer class is the adaptee that can only play cassette tapes. The CassetteToMP3Adapter class implements the target interface and contains an instance of the adaptee class, which allows it to adapt the cassette player to work as an MP3 player.

    When to use Adapter Pattern?

    You should consider using the Adapter pattern in the following scenarios −

    • When you want to use an existing class, but its interface does not match the one you need.
    • To create a reusable class that can work with different interfaces.
    • While integrating third-party libraries or legacy code that have incompatible interfaces.
    • Decoupling the client code from the implementation details of the adaptee class.
    • When you want to provide a standard interface for a set of classes with different interfaces.

    Real World Applications of Adapter Pattern

    Some real-world applications in software development where the Adapter pattern is commonly used are shown in the below image −

    Uses Cases of Adapter Pattern

    Conclusion

    This chapter was about the Adapter design pattern. It’s a structural pattern that helps in making incompatible things work together. We looked at both the Class Adapter and Object Adapter with C++ examples. The Adapter pattern is very useful when working with old code or third-party libraries that don’t match our interface. In short, it makes things work together that normally wouldn’t.

  • Chain of Responsibility in C++

    The Chain of Responsibility pattern is a behavioral design pattern which allows you pass a object of a request through a chain of potential handlers until one of them handles the request. This pattern decouples the sender of a request from its receiver by giving multiple objects a chance to handle the request.

    In this pattern, each handler in the chain has a reference to the next handler. When a request is received, the handler decides either to process the request or to pass it to the next handler in the chain. This continues until a handler processes the request or the end of the chain is reached.

    Chain of Responsibility Pattern Diagram

    Components of the Chain of Responsibility Pattern

    There are three main components in the Chain of Responsibility pattern, we have listed them below −

    • Handler − It can be an abstract class or it may be an interface which defines a method for handling requests and a method for setting the next handler in the chain.
    • Concrete Handler − These are the classes that implement the handler interface and provide specific implementations for handling requests. Each concrete handler decides whether to process the request or pass it to the next handler.
    • Client − The client is responsible for creating the chain of handlers and initiating the request processing by sending the request to the first handler in the chain.

    Implementation of the Chain of Responsibility Pattern

    Now, let’s implement the Chain of Responsibility pattern in C++.

    In this example, we will take an real-world scenario where, we have a support ticket system. The tickets can be handled by different levels of support staff based on the priority and complexity of the issue.

    Steps to Implement the Chain of Responsibility Pattern in C++

    Following are the steps to implement the Chain of Responsibility pattern in C++:

    Steps to Implement Chain of Responsibility Pattern

    C++ Implementation of Chain of Responsibility Pattern

    Here is a simple implementation of the Chain of Responsibility pattern in C++ of the support ticket system −

    #include <iostream>#include <string>usingnamespace std;// Abstract HandlerclassSupportHandler{protected:
       SupportHandler* nextHandler;public:SupportHandler():nextHandler(nullptr){}voidsetNextHandler(SupportHandler* handler){
          nextHandler = handler;}virtualvoidhandleRequest(const string& issue,int priority)=0;};// Concrete Handler: Level 1 SupportclassLevel1Support:public SupportHandler{public:voidhandleRequest(const string& issue,int priority)override{if(priority ==1){
             cout <<"Level 1 Support handled the issue: "<< issue << endl;}elseif(nextHandler){
             nextHandler->handleRequest(issue, priority);}}};// Concrete Handler: Level 2 SupportclassLevel2Support:public SupportHandler{public:voidhandleRequest(const string& issue,int priority)override{if(priority ==2){
             cout <<"Level 2 Support handled the issue: "<< issue << endl;}elseif(nextHandler){
             nextHandler->handleRequest(issue, priority);}}};// Concrete Handler: Level 3 SupportclassLevel3Support:public SupportHandler{public:voidhandleRequest(const string& issue,int priority)override{if(priority ==3){
             cout <<"Level 3 Support handled the issue: "<< issue << endl;}elseif(nextHandler){
             nextHandler->handleRequest(issue, priority);}}};intmain(){// Create handlers
       Level1Support level1;
       Level2Support level2;
       Level3Support level3;// Set up the chain of responsibility
       level1.setNextHandler(&level2);
       level2.setNextHandler(&level3);// Create some support requests
       level1.handleRequest("Password reset",1);
       level1.handleRequest("Software installation",2);
       level1.handleRequest("System crash",3);
       level1.handleRequest("Unknown issue",4);// No handler for this priorityreturn0;}

    In this example, we have defined an abstract handler class SupportHandler with a method to handle requests and a method to set the next handler. We then created three concrete handlers: Level1SupportLevel2Support, and Level3Support, each responsible for handling requests of different priority levels.

    In the main function, we created instances of each handler and set up the chain of responsibility. Finally, we created some support requests with different priority levels to demonstrate how the requests are handled by the appropriate handlers in the chain.

    Pros and Cons of Chain of Responsibility Pattern

    Here are some pros and cons of using the Chain of Responsibility pattern −

    ProsCons
    Makes it easy to separate who sends a request from who handles itCan be tricky to figure out where a request was handled
    Lets you add or remove support levels without much hassleIf the chain gets too long, it might slow things down
    Simple to organize and keep your code tidySome requests might not get handled at all
    You can change the order of handlers whenever you needIt’s not always obvious how the request moves through the chain
    Each handler can focus on just one thingYou need to plan carefully how handlers work together
    You can reuse the same handler in different chainsHaving lots of handlers might use more memory

    Real-World Examples of Chain of Responsibility Pattern

    Here are some real-world examples where the Chain of Responsibility pattern is commonly used −

    • Event Handling in Apps − When you click a button or press a key, the event travels through a series of handlers. The first one that knows what to do with it takes care of it, just like passing a message down a line until someone responds.
    • Logging − When something happens in a program, a log message might go through several loggers—one for errors, one for warnings, one for info—until it finds the right place to be recorded.
    • Customer Support − If you’ve ever contacted customer support, you know your question might get passed from one person to another until someone can actually help you. That’s the chain of responsibility in action.
    • Web Requests − When you visit a website, your request can go through different steps—like checking if you’re logged in, filtering out bad requests, or adding extra info—before it gets to the part that shows you the page.

    Conclusion

    In this chapter, we’ve seen what the Chain of Responsibility pattern is, how it works, and how to use it in C++. It’s a handy way to keep your code flexible and easy to maintain by letting requests find the right handler without everyone needing to know about each other.

  • C++ unordered_multiset

    std::unordered_multiset

    An unordered_multiset is a container by the Standard Template Library (STL) in C++, which stores elements without any particular order and allows multiple occurrences or duplicate values of the same element. The <unordered_set> header file is used for both unordered_set and unordered_multiset containers.

    Syntax

    Here is the following syntax for declaring an unordered_multiset:

    #include <unordered_set>
    std::unordered_multiset<type> container_name;

    Here,

    Example to Create unordered_multiset

    The following example demonstrates how you can create an unordered multiset:

    #include <unordered_set>#include <iostream>#include <string>usingnamespace std;intmain(){// Declare unordered_multisets for integers and strings
        unordered_multiset<int> ums_int ={1,2,3,3};
        unordered_multiset<string> ums_str ={"apple","banana","apple"};// Display both sets together (merged display)
        cout <<"Merged unordered_multiset (Integers and Strings): ";for(constauto& elem : ums_int){
            cout << elem <<" ";}for(constauto& elem : ums_str){
            cout << elem <<" ";}
        cout << endl;return0;}

    Output

    Merged unordered_multiset (Integers and Strings): 3 3 2 1 banana apple apple
    

    Member Functions of unordered_multiset

    1. The insert() function

    The insert() function is used to add one or more elements to the unordered_multiset, allowing duplicity.

    Syntax

    unordered_multiset<type> ums;
    ums.insert(element);
    ums.insert({element1, element2,...});

    2. The find() function

    The find() function is used to check if an element exists in the unordered_multiset. It returns an iterator to the element if found; otherwise, it returns end().

    Syntax

    auto it = ums.find(element);// Returns iterator to element or ums.end()

    3. The count() function

    The count() function returns the number of occurrences of an element in the unordered_multiset. Since this function allows duplicacy, it will return the total count of that element in the container.

    Syntax

    size_t count = ums.count(element);// Returns number of occurrences of element

    4. The erase() function

    The erase() function removes one or more elements from the unordered_multiset. You can remove specific elements by value or use an iterator to remove a single element. For duplicates, only one occurrence will be removed for each call.

    Syntax

    ums.erase(element);// Removes one occurrence of element
    ums.erase(it);// Removes the element at iterator `it`
    ums.erase(ums.begin(), ums.end());// Removes all elements in the range

    5.The find() or count() function

    This function will check if an element exists in the container.

    Syntax

    list.count(element);// for lists
    string.count(substring);// for strings

    6. The size() function

    The size() function returns the number of elements currently in the unordered_multiset.

    Syntax

    size_t size = ums.size();// Returns the number of elements in the set

    7. The begin() and end() functions

    To iterate, you can use iterators or range-based loops over the elements in an unordered_multiset. begin() and end() are used for accessing the first and last elements.

    Syntax

    for(auto it = ums.begin(); it != ums.end();++it){// Access each element via *it}

    unordered_set Vs. unordered_multiset

    • unordered_set: A container that stores unique elements only, no duplicates are allowed.
    • unordered_multiset: A container that allows multiple occurrences (duplicates) of the same element.

    Average Time Complexity of unordered_multiset

    unordered_multiset is implemented using a hash table data structure. This results in fast access because elements are hashed into “buckets” based on their hash values. This unordered_multiset provides constant time complexity, i.e., O(1), for some generic operations like lookups, insertions, and deletions.

    Hash collisions lead to the worst time complexity O(n). This is because, in a collision, multiple elements are hashed into the same bucket, which results in a linear search through all the elements in that bucket. This overall results in a significant degradation of performance.

    Use Cases of unordered_multiset

    • It is used when you want to store multiple occurrences of the same element.
    • It is suitable for counting the frequency of items or when duplicates are required.
    • It is efficient for quick lookups and insertions with no need for sorting.
  • nullptr in C++

    The nullptr keyword in C++ represents a null pointer value which was earlier represented using NULL or 0. It was introduced in C++11 and is of type std::nullptr_t. The nullptr is a type-safe pointer that is implicitly convertible and can be compared to any pointer.

    The syntax for defining a nullptr is given below −

    int* ptr =nullptr;// ptr is a null pointer of type int*

    Example

    The following example demonstrates how to use nullptr in C++

    #include <iostream>usingnamespace std;intmain(){int*ptr =nullptr;// ptr is a null pointerif(ptr ==nullptr){
    		cout <<"Pointer is null."<< endl;}else{
    		cout <<"Pointer is not null."<< endl;}int x =10;
    	ptr =&x;if(ptr !=nullptr){
    		cout <<"Pointer now points to: "<<*ptr 
                 << endl;}return0;}

    The output of the above code is given below −

    Pointer is null.
    Pointer now points to: 10
    

    Why Do We Need nullptr?

    We need nullptr because of the following problems caused by using NULL or 0

    Ambiguity in Function Calling

    Calling a function with value ‘0’ to represent NULL creates an ambiguity because compiler treats ‘0’ as an integer value rather than a null pointer.

    In this example, we are calling display() function by passing value as ‘0’. It calls the function that has an int parameter rather than calling the function that has a pointer in its parameter.

    #include <iostream>usingnamespace std;voiddisplay(int n){
       cout <<"Calling display function with int"<< endl;}voiddisplay(int*p){
       cout <<"Calling display function with int*"<< endl;}intmain(){display(0);// Ambiguous call as Compiler treats 0 as integerreturn0;}

    The output of the above code is given below −

    Calling display function with int
    

    Solution: Below is the solution to above problem where we have used nullptr that calls the display() function that has a pointer in its parameter −

    #include <iostream>usingnamespace std;voiddisplay(int n){
       cout <<"Calling display function with int"<< endl;}voiddisplay(int*p){
       cout <<"Calling display function with int*"<< endl;}intmain(){display(nullptr);return0;}

    The output of the above code is given below −

    Calling display function with int*
    

    Problem in Function Overloading

    If you call an overloaded function with NULL value, it will cause a compilation error. The NULL is valid for both parameters that is int and int*. So, in this confusion compiler throws an error. Below is an example to demonstrate this problem.

    #include <iostream>usingnamespace std;classDemo{public:voidshow(int n){
    		cout <<"Calling show() function with int"<< endl;}voidshow(int*p){
    		cout <<"Calling show() function with int*"<< endl;}};intmain(){
    	Demo obj;// Ambiguous call with NULL
    	obj.show(NULL);return0;}

    The output of the above code is given below −

    main.cpp: In function 'int main()':
    main.cpp:22:17: error: call of overloaded 'show(NULL)' is ambiguous
       22 |         obj.show(NULL);
          |         ~~~~~~~~^~~~~~
    main.cpp:7:14: note: candidate: 'void Demo::show(int)'
        7 |         void show(int n)
          |              ^~~~
    main.cpp:12:14: note: candidate: 'void Demo::show(int*)'
       12 |         void show(int *p)
          |              ^~~~
    

    Solution: Below is the solution to above problem where we have used nullptr that calls the display() function that has a pointer in its parameter −

    #include <iostream>usingnamespace std;classDemo{public:voidshow(int n){
    		cout <<"Calling show() function with int"<< endl;}voidshow(int*p){
    		cout <<"Calling show() function with int*"<< endl;}};intmain(){
    	Demo obj;
    	obj.show(nullptr);// Correctly calls int* functionreturn0;}

    The output of the above code is given below −

    Calling show() function with int*
    

    Type-Safety Problem

    The NULL is treated as an integer since it is defined with ‘0’. It creates the same ambiguity as the above two problems. It is solved using nullptr because nullptr is type-safe unlike NULL and it is implicitly convertible to any pointer type.

    #include <iostream>usingnamespace std;intmain(){int* ptr =nullptr;
    
    	cout <<"Comparing with NULL"<< endl;if(ptr ==NULL){
    		cout <<"NULL is TRUE"<< endl;}else{
    		cout <<"NULL is FALSE"<< endl;}
    
    	cout <<"\nComparing with nullptr"<< endl;if(ptr ==nullptr){
    		cout <<"nullptr is TRUE"<< endl;}else{
    		cout <<"nullptr is FALSE"<< endl;}int value =0;// NULL will be treated as integer 0if(value ==NULL){
    		cout <<"value(0) = NULL is TRUE"<< endl;}return0;}

    The output of the above code is given below −

    main.cpp: In function 'int main()':
    main.cpp:24:22: warning: NULL used in arithmetic [-Wpointer-arith]
       24 |         if (value == NULL) 
          |                      ^~~~
    Comparing with NULL
    NULL is TRUE
    
    Comparing with nullptr
    nullptr is TRUE
    value(0) = NULL is TRUE
    

    NULL vs nullptr

    The difference between NULL and nullptr is mentioned in the table below −

    NULLnullptr
    It is an integer constant that represents either 0 or 0L. In C, it is represented as ((void*)0).It is a pointer literal of type std::nullptr_t.
    It is not type-safe as it may be treated as an int.It is type-safe as it represents only pointer.
    It can be assigned to int values.It can not be assigned to an int value, as it will show an error.
    In function overloading, there is ambiguity when calling the function.It prevents the problem of ambiguity in function overloading.
    Example: int* p = NULL;Example: int* p = nullptr;

    Nullptr Use Cases

    The main purpose of the nullptr is to assign a null value to any pointer. Here are some use cases where a nullptr can be used.

    Resetting Pointers After Deletion

    A pointer needs to be assigned to a null value after deletion to avoid a dangling pointer. Here is an example to reset pointer to null after cleaning up the memory.

    #include <iostream>usingnamespace std;intmain(){int*ptr =newint(57);
    	cout <<"Value: "<<*ptr << endl;delete ptr;// Free memory
    	ptr =nullptr;// Resetting pointerif(ptr ==nullptr)
    		cout <<"Pointer reset successful."<< endl;return0;}

    The output of the above code is given below −

    Value: 57
    Pointer reset successful.
    

    Checking Pointer Validity

    You should first check that the pointer is not null before accessing it to avoid crashes. Here is an example to check if the pointer ptr is a null pointer or not.

    #include <iostream>usingnamespace std;intmain(){int*ptr =nullptr;if(ptr !=nullptr)
    		cout <<"Pointer value: "<<*ptr << endl;else
    		cout <<"It is a null pointer."<< endl;return0;}

    The output of the above code is given below −

    It is a null pointer.	
    

    Safe Object Initialization for Null Value

    The nullptr can be used for setting a pointer to null without causing any error or any garbage value. Here is an example to assign a null value to represent an empty linked list −

    #include <iostream>usingnamespace std;structNode{int data;
    	Node *next;};intmain(){
    	Node *head =nullptr;// Empty linked listif(head ==nullptr)
    		cout <<"Linked list is empty."<< endl;return0;}

    The output of the above code is given below −

    Linked list is empty.
    

    Function Overloading Resolution

    The nullptr is also used to resolve the ambiguity in function overloading. Here is an example.

    #include <iostream>usingnamespace std;voidshow(int n){
       cout <<"Integer called."<< endl;}voidshow(int*p){
       cout <<"Pointer called."<< endl;}intmain(){show(nullptr);// Calls pointerreturn0;}

    The output of the above code is given below −

    Pointer called.
    

    Conclusion

    In this chapter, we have understood that the nullptr in C++ is used when we need a null pointer. Before C++11, NULL was used, but it had various problems which is addressed by the nullptr. We have also discussed various use cases of nullptr with examples.

  • Lambda Expression in C++

    Lambda Expression

    A lambda expression in C++11 allows the user to define an anonymous function (a function without any name) inline, which captures variables from the surrounding scope. This makes them a powerful feature for various use cases, like callbacks, sortingfunctional programming, etc.

    Syntax

    Here is the syntax of lambda expression in C++:

    [capture](parameters)-> return_type 
    { 
        function_body 
    }

    Where,

    • capture specifies which variables from the outer scope are captured. It captures variables by value, by reference, or by both methods.
    • parameters are the input parameters for lambda.
    • return_type defines the return type of the lambda function. If the return type needs to be explicitly defined, it will follow this -> symbol.
    • body is the main body of the lambda, where function logic is written.

    Example of Lambda Expression

    In the following example, a lambda expression is used to add two numbers and return the result:

    #include <iostream>intmain(){// Define a lambda expression to add two numbersauto add =[](int a,int b){return a + b;};// Call the lambda expressionint result =add(5,3);
    
        std::cout <<"The sum of 5 and 3 is: "<< result << std::endl;return0;}

    Output

    The sum of 5 and 3 is: 8

    Capturing Variables in Lambda Expression

    Capturing variables in lambda expressions allows lambda to access variables from its surrounding scope. By a capture clause, a lambda can capture variables from its surrounding scope and allow it to use those variables inside the lambda body.

    Types of Variable Capture:

    1. Capture by value ([x])

    It captures the variables by values, which means lambda gets a copy of the variables and further cannot modify the original variable outside the lambda.

    [x](parameters)-> return_type { body }

    2. Capture by reference ([&x])

    It captures the variables by reference, which means here the lambda can access and modify the original variables.

    [&x](parameters)-> return_type { body }

    3. Capture Specific Variables ([x, &y])

    This allows you to mix capture types in the same lambda. Here, the user can specify which variables to capture by value or reference.

    [x,&y](parameters)-> return_type { body }

    4. Captures all variables by value ([=])

    It capturesallvariables in the surrounding scope by value.

    [=](parameters)-> return_type { body }

    5. Captures all variables by reference ([&])

    It captures all variables in the surrounding scope by reference.

    [&](parameters)-> return_type { body }

    Capture this by reference ([this])

    It captures this pointer (a reference to the current object) in a lambda expression. It is useful when the user needs to access member variables or functions from within a lambda in a class method.

    6. Capture by Mixed Modes

    • [=, &x] ,It captures all variables by value and x variable by reference.
    • [&, x] ,It captures all variables by reference and x by value.
    • [=, this] ,It captures this pointer by value and all other variables by value.

    Return Types in Lambda Expressions

    In C++, lambda expressions return the value just like regular functions, and its return type can be automatically deduced by the compiler or explicitly specified by the programmer.

    1. Automatic Return Type Deduction

    In this, the compiler deduces the return type based on the return expression inside the lambda.

    a) Implicit Return Type

    In this, the return type is based on the return expression, which means users don’t need to explicitly specify the return type; it will automatically be inferred from the type of the expression.

    [capture](parameters){return expression;}

    b) Returning References

    It returns references to variables or values; for this, make sure that the referenced variable stays in scope for the lifetime of the lambda.

    [capture](parameters)-> type&{return reference;}

    c) Returning Pointers

    A lambda can also return a pointer to a variable or dynamically allocated memory.

    [capture](parameters)-> type*{return pointer;}

    d) Type Deduction with auto

    Here, you can also use an auto keyword for the return type, and the compiler will deduce the correct return value type based on an expression.

    [capture](parameters)->auto{return value;}

    2. Explicit Return Type

    In this, if the user wants to specify lambda’s return type explicitly, then use -> return_type syntax. This is useful when working with any complex types and when the return type isn’t obvious.

    [capture](parameters)-> return_type {return expression;}

    Example

    #include <iostream>usingnamespace std;intmain(){int x =5;int y =10;auto my_lambda =[=,&x]()->int{
            cout <<"Inside lambda:"<< endl;// can't modify 'y' as it's captured by value// Modifying 'x' as it's captured by reference
            x +=10;
            cout <<"Captured 'x' by reference inside lambda: "<< x << endl;// Captured 'y' by value, so it can't be modified here// simple operation with 'y' and a local valueint sum = y +5;
            cout <<"Captured 'y' by value inside lambda: "<< y << endl;
            cout <<"Sum of 'y' and 5 inside lambda: "<< sum << endl;return sum;};// Call the lambdaint result =my_lambda();
        cout <<"Result returned from lambda: "<< result << endl;
        cout <<"Value of 'x' outside lambda after modification: "<< x << endl;
        cout <<"Value of 'y' outside lambda (no modification): "<< y << endl;return0;}

    When the above code is compiled and executed, it produces the following result −

    Inside lambda:
    Captured 'x' by reference inside lambda: 15
    Captured 'y' by value inside lambda: 10
    Sum of 'y' and 5 inside lambda: 15
    Result returned from lambda: 15
    Value of 'x' outside lambda after modification: 15
    Value of 'y' outside lambda (no modification): 10

    Recursive Lambdas

    In C++, recursive lambda is the lambda function that calls itself over again and again during its execution until it reaches its base case. As lambda by default cannot call themselves directly, because they don’t have a name. So for this, we can make lambda recursive by using a function pointer or std::function.

    Example

    #include <iostream>#include <functional> // for std::functionusingnamespace std;intmain(){// Defining the recursive lambda using std::function
        std::function<int(int)> factorial =[&](int n)->int{if(n <=1)return1;// Base casereturn n *factorial(n -1);// Recursive call};
        cout <<"Factorial of 5: "<<factorial(5)<< endl;}

    When the above code is compiled and executed, it produces the following result −

    Factorial of 5: 120