Message Passing in C++: Working & Implementation

Published: March 6, 2025 | Reading Time: 4 minutes

Message passing is a fundamental concept in C++ and a key aspect of object-oriented programming. It involves objects communicating through the exchange of messages, typically via method or function calls. This mechanism encourages collaboration among objects and supports a modular and flexible software design. In this comprehensive guide, we'll explore the essentials of message passing in C++, examine its advantages, and discuss how it can enhance the efficiency of software development.

Table of Contents

What is a Message?

A message is a block of data or a well-structured request by one object (the sender) to another (the receiver) for invoking a specific function or executing a particular task. Messages can be transmitted through different channels, such as local networks, remote networks, shared memory, or inter-process communication. They serve to signal events, requests, or responses between components, as well as for synchronization and coordination.

Critical Components of a Message

The critical components of a message include:

What is Message Passing in C++?

Message passing is the process of communicating information between objects through a specific logical entity called a message. This mechanism is crucial for building reliable and modular software systems. It is also a central notion in object-oriented programming and encapsulation, which involves interfacing objects through well-defined public methods or functions instead of their internal details.

Example Implementation of Message Passing in C++

Here is the implementation of message passing in C++ with an example program:

#include <iostream>
#include <string>

class Car {
public:
    void accelerate() {
        std::cout << "The car is accelerating." << std::endl;
    }

    void stop() {
        std::cout << "The car has stopped." << std::endl;
    }
};

class Driver {
public:
    void sendMessage(Car& car, const std::string& command) {
        if (command == "accelerate") {
            car.accelerate(); // Sending a message to accelerate
        } else if (command == "stop") {
            car.stop(); // Sending a message to stop
        } else {
            std::cout << "Unknown command." << std::endl;
        }
    }
};

int main() {
    Car myCar;
    Driver driver;

    driver.sendMessage(myCar, "accelerate"); // Sending a message to the car
    driver.sendMessage(myCar, "stop");        // Sending another message to stop

    return 0;
}

Explanation

Output

The car is accelerating.
The car has stopped.

How Message Passing Works in C++

Message passing differs from the previous two methods. It allows objects to share data by exchanging messages. These steps involve:

  1. Creating classes: The developer designs classes that represent the communication objects
  2. Defining methods: The classes should have suitably designed methods for message interaction
  3. Sending messages: Objects use methods or parameters to send data to other objects
  4. Receiving and processing messages: The receiving object handles the message by calling the method to process it

Types of Message Passing in C++

The message passing can be two types:

Aspect Synchronous Message Passing Asynchronous Message Passing
Waiting Behavior The sender waits for the receiver to get the message The sender does not wait for the receiver's acknowledgement before execution continues
Blocking Blocks the sender waits until the receiver acknowledges receipt Non-blocking; the sender continues immediately
Synchronization Provides for synchronisation of the involved parties during communication Enables independent operation of sender and receiver
Implementation It is simpler to implement, and flow control is relatively easy More complex; needs mechanisms for handling delivery and ordering
Use Cases Suitable for applications needing immediate feedback and confirmation It is ideal for applications where responsiveness and parallelism are prioritised
Performance The sender will always create delays if it waits and the receiver is busy, thus affecting performance Improves throughput by allowing multiple operations to proceed simultaneously

Message Passing in C++ Using Boost Libraries

The code implementation of message passing in C++ through Boost Libraries, i.e., Boost.Interprocess for inter-thread and Boost.Asio for network communication (server-client approach):

#include <boost/interprocess/ipc/message_queue.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <thread>
#include <chrono>

using namespace boost::interprocess;
using namespace boost::asio;
using ip::tcp;

void sender() {
    message_queue mq(open_or_create, "mq", 100, sizeof(int));

    for (int i = 0; i < 10; ++i) {
        mq.send(&i, sizeof(i), 0);
        std::cout << "Sent: " << i << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void receiver() {
    message_queue mq(open_or_create, "mq", 100, sizeof(int));

    for (int i = 0; i < 10; ++i) {
        int msg;
        size_t size;
        unsigned int prio;
        mq.receive(&msg, sizeof(msg), size, prio);
        std::cout << "Received: " << msg << std::endl;
    }
}

void server() {
    io_service ios;
    tcp::acceptor acc(ios, tcp::endpoint(tcp::v4(), 12345));
    std::cout << "Waiting for connection..." << std::endl;

    tcp::socket sock(ios);
    acc.accept(sock);
    std::cout << "Client connected!" << std::endl;

    char buf[512];
    size_t len = sock.read_some(boost::asio::buffer(buf));
    std::cout << "Received: " << std::string(buf, len) << std::endl;
}

void client() {
    io_service ios;
    tcp::resolver res(ios);
    tcp::resolver::query qry("127.0.0.1", "12345");
    tcp::resolver::iterator it = res.resolve(qry);

    tcp::socket sock(ios);
    connect(sock, it);

    std::string msg = "Hello!";
    sock.send(boost::asio::buffer(msg));
    std::cout << "Sent: " << msg << std::endl;
}

int main() {
    std::thread t1(sender);
    std::thread t2(receiver);

    t1.join();
    t2.join();

    message_queue::remove("mq");

    std::thread s(server);
    std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Wait for server to start
    std::thread c(client);

    c.join();
    s.join();

    return 0;
}

Explanation

Here is the explanation of boost libraries code:

Boost.Interprocess

Boost.Asio

Output

//Sender thread will print
Sent: 0
Sent: 1
Sent: 2
Sent: 3
Sent: 4
Sent: 5
Sent: 6
Sent: 7
Sent: 8
Sent: 9
//Receiver thread will print
Received: 0
Received: 1
Received: 2
Received: 3
Received: 4
Received: 5
Received: 6
Received: 7
Received: 8
Received: 9
//Server prints
Waiting for connection...
Client connected!
Received: Hello!
//Client prints
Sent: Hello!

Inter-Thread Message Passing in C++

Here is an inter-thread message passing example in C++:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> dataQueue;
bool ready = false;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Simulate work
        std::lock_guard<std::mutex> lock(mtx);
        dataQueue.push(i);
        ready = true; // Signal that data is ready
        cv.notify_one(); // Notify consumer
        std::cout << "Produced: " << i << std::endl;
    }
}

void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return ready; }); // Wait until data is ready

        while (!dataQueue.empty()) {
            int data = dataQueue.front();
            dataQueue.pop();
            std::cout << "Consumed: " << data << std::endl;
        }

        ready = false; // Reset the flag after consuming all data
    }
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);

    t1.join();
    t2.join();

    return 0;
}

Explanation

The above C++ program implements a producer-consumer model using threads, mutexes, and condition variables. The producer() function generates integers from 0 to 9 and puts them in a shared queue, telling the consumer() when data can be processed. The consumer() waits until the producer signals, then takes the data out of the queue for processing. Locking mechanisms ensure that one thread locks its sections of access to the shared queue while waiting for the other threads to unlock theirs. Condition variables ensure the synchronisation of the interaction between the producer and consumer threads.

Output

Produced: 0
Produced: 1
Produced: 2
Produced: 3
Produced: 4
Produced: 5
Produced: 6
Produced: 7
Produced: 8
Produced: 9
Consumed: 0
Consumed: 1
Consumed: 2
Consumed: 3
Consumed: 4
Consumed: 5
Consumed: 6
Consumed: 7
Consumed: 8
Consumed: 9

What are Message Queues in C++?

A message queue is a data structure that enables communication among performing independent units: either threads or processes. It lets units send or receive messages. They act as a holding area or buffer for messages until they are processed and provide asynchronous communication. Message queues can be based, for instance, on a queue made with standard library containers like std::queue, or they can be implemented from scratch when a specific set of requirements must be addressed.

Difference Between a Method Call and Message Passing

Here are the key differences between method call and message passing:

Aspect Method Call Message Passing
Definition A method call means a direct call of a member function of an object Message passing is a broader concept that allows inter-communication between objects through messages
Scope Method calls represent a specific way to invoke behaviour on an object Message Passing considers various communication mechanisms, including events and signals
Communication Type It involves direct communication between two objects It can involve multiple forms of communication, allowing for more flexible interactions
Coupling The sender must explicitly know the receiver and call its method directly The sender raises an event or sends a message that any subscribed receiver may handle
Flexibility The receiver has a specific function defined to handle the call Different receivers can respond differently to the same message, providing greater flexibility
Relationship A method call is a particular instance of message passing focused on function invocation Message passing is a general term comprising any mechanism for object communication
Parameters Parameters are passed directly with the method call to provide necessary data Parameters can be included in the message, allowing the receiver to handle them differently

What are Message Design Patterns?

Messaging design patterns provide solutions to common communication challenges in distributed systems. These patterns facilitate the message exchange between components, so they are in high demand because they provide loose coupling and scalability in software development. Here are the message design patterns:

1. Publish-Subscribe Pattern

The publish-subscribe pattern allows multiple subscribers to receive messages from one point (a broker). This allows decoupling and high scaling levels because direct connections between subscribers and publishers are discarded.

2. Message Queue Pattern

The message queue pattern decouples producers from consumers, allowing for reliable and asynchronous message passing. Messages are queued until consumers are ready to process them, preventing data loss.

3. Request-Reply Pattern

The request-reply pattern provides synchronous communication in which one component requests a response from another and waits for a response. Popular usage includes client-server architectures.

4. Message Broker Pattern

The message broker pattern is an intermediary that manages the exchange of messages between producers and consumers, providing routing, filtering, and transformation capabilities for more complex interactions.

5. Command Pattern

The command pattern assigns requests to one of the objects; it allows queuing, parameterisation, and implementing undo functionality. This increases the flexibility of handling commands in applications.

Best Practices for Message Passing in C++

Here are some best practices for message passing in C++:

Conclusion

In conclusion, message passing in C++ is an essential paradigm that facilitates communication between objects and processes, building greater modularity and flexibility in software design. By understanding its principles and implementing effective strategies, developers may develop robust applications that handle these complex interactions, remain performant, and provide decent reliability.

Frequently Asked Questions

What are the techniques to define message passing in C++?

The message-passing technique involves sending a request from one object to another to perform an operation or return data.

What is meant by passing a message?

Passing a message means invoking a method or function of another object by sending some kind of request, often carrying some data.

What are the three types of messages?

The three types of messages are:

What is the difference between dynamic binding and message passing in C++?

Dynamic Binding refers to the methods in C++. The method call resolves at runtime, while polymorphism uses virtual functions. Message Passing tells the objects to communicate by invoking methods on each other. In dynamic binding, method resolution is based on a runtime; message passing includes sending requests for action and/or data between two objects.


Source: NxtWave - CCBP Blog

Original URL: https://www.ccbp.in/blog/articles/message-passing-in-cpp