C++ Active Object Pattern: Real-World Engineering Guide

Problem Solved

Avoid shared-state bugs by pushing tasks into an object’s private thread, ensuring sequential message processing.

How It Works

  • Each object has its own thread and an event queue
  • Method calls become messages queued to the object
  • Object processes messages sequentially in its thread

STL Usage

#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <atomic>
using namespace std;

class ActiveObject {
private:
    queue<function<void()>> message_queue_;
    mutex mtx_;
    condition_variable cv_;
    thread active_thread_;
    atomic<bool> running_{true};

    void run() {
        while (running_) {
            function<void()> message;
            {
                unique_lock<mutex> lock(mtx_);
                cv_.wait(lock, [this]() { return !message_queue_.empty() || !running_; });
                
                if (!running_ && message_queue_.empty()) break;
                
                message = message_queue_.front();
                message_queue_.pop();
            }
            message();
        }
    }

public:
    ActiveObject() : active_thread_([this]() { run(); }) {}

    void send(function<void()> message) {
        {
            lock_guard<mutex> lock(mtx_);
            message_queue_.push(message);
        }
        cv_.notify_one();
    }

    ~ActiveObject() {
        running_ = false;
        cv_.notify_one();
        active_thread_.join();
    }
};

// Example active object
class BankAccount {
private:
    ActiveObject active_;
    int balance_ = 0;

public:
    void deposit(int amount) {
        active_.send([this, amount]() {
            balance_ += amount;
        });
    }

    void withdraw(int amount, function<void(bool)> callback) {
        active_.send([this, amount, callback]() {
            bool success = balance_ >= amount;
            if (success) balance_ -= amount;
            callback(success);
        });
    }

    void getBalance(function<void(int)> callback) {
        active_.send([this, callback]() {
            callback(balance_);
        });
    }
};

Example

#include <iostream>
using namespace std;

void activeObjectExample() {
    BankAccount account;
    
    account.deposit(100);
    account.withdraw(50, [](bool success) {
        cout << "Withdrawal " << (success ? "succeeded" : "failed") << endl;
    });
    
    account.getBalance([](int balance) {
        cout << "Balance: " << balance << endl;
    });
    
    this_thread::sleep_for(chrono::milliseconds(100));
}

Use Cases

  • GUI toolkits: UI objects process messages in UI thread
  • Actor systems: Each actor is an active object
  • Game engines: Game objects process updates sequentially
  • State machines: Sequential state transitions

Key Takeaways

  • Eliminates shared-state bugs
  • Sequential message processing
  • Thread-safe by design
  • Common in actor systems

Things to Be Careful About

  • Message ordering: Messages processed in order
  • Deadlocks: Circular message dependencies
  • Performance: Message queuing overhead
  • Callback lifetime: Ensure callbacks remain valid

Summary

Active Object pattern eliminates shared-state issues by ensuring sequential message processing in object’s own thread.