Post

Bridge Design Pattern in C++

Bridge Design Pattern in C++

In this code demonstration, I showcase the application of the Bridge design pattern within my DirectoryPoller. The shift from inheritance to object composition enhances the separation of abstraction and implementation. While resembling PIMPL initially, the key distinction lies in the inclusion of the AbstractPoller class, enabling support for multiple implementations. By dynamically swapping the implementation pointer, my class can alter its mechanism at runtime, a flexibility not intrinsic to the singular implementation focus of PIMPL designed for breaking compilation dependencies. The Bridge pattern, with its dynamic adaptability, emerges as an elegant solution for orchestrating the nuanced interplay between abstraction and implementation in this codebase.

Let’s commence by establishing the interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef POLLER_BRIDGE_H
#define POLLER_BRIDGE_H

#include <thread>
#include "AbstractPoller.h"
#include "impl/PollResult.h"

class PollerBridge
{
public:
  PollerBridge(const PollerBridge &original); // Copy constructor
  PollerBridge(const AbstractPoller &innerReader); // Implicit constructor
  inline void set_queue(SafeQueue<PollResult> *queue); // Thread-safe messaging queue
  bool start(); // Method for starting AbstractPoller run() method in a separate thread
  void join() const; // Method for joining thread
  PollerBridge &operator=(const PollerBridge &original); // Copy assignment
  ~PollerBridge();

private:
  AbstractPoller *m_poller_ptr; // Pointer to implementation
  std::unique_ptr<std::thread> m_t;
};

inline void PollerBridge::set_queue(SafeQueue<PollResult> *queue)
{
  return m_poller_ptr->set_queue(queue);
}
#endif

And here is the associated implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "PollerBridge.h"
#include "ThreadGuard.h"

PollerBridge::PollerBridge(const PollerBridge &original)
{
  m_poller_ptr = original.m_poller_ptr->clone();
}

PollerBridge::PollerBridge(const AbstractPoller &inner_poller)
{
  m_poller_ptr = inner_poller.clone();
}

bool PollerBridge::start()
{
  m_t = std::make_unique<std::thread>(&AbstractPoller::run, m_poller_ptr);
  return true;
}

void PollerBridge::join() const { ThreadGuard g(*m_t); }

PollerBridge::~PollerBridge() { delete m_poller_ptr; }

The concept revolves around encapsulating my DirectoryPoller implementation within the PollerBridge, wherein the start() method is instrumental in executing the poller in a distinct thread.

1
2
3
4
5
6
poller.start();

...

// Join
poller.join();

ThreadGuard serves as a encapsulating class, ensuring the proper handling of threads by guaranteeing their joining and preventing inadvertent destruction in the event of an exception. This precaution is necessary as a call to join() might be overlooked if an exception occurs after the thread initiation but before the join() call. To avert potential issues with thread lifetimes and prevent the premature termination of the application, join() is invoked even in the presence of an exception.

There are two possible approaches, and one option is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct func;
void f() {
  int some_local_state = 0;
  func my_func(some_local_state);
  std::thread t(my_func);
  try {
      do_something_in_current_thread();
  } catch(...) {
      t.join();
      throw;
  }
  t.join();
}

Alternatively, as demonstrated in this class, the approach involves leveraging RAII principles. A dedicated class is designed to execute the join() operation within its destructor:

1
2
3
4
5
6
7
8
9
10
struct func;
void f() {
  int some_local_state = 0;
  func my_func(some_local_state);
  std::thread t(my_func);

  ThreadGuard g(t);

  do_something_in_current_thread();
}

Here is the complete ThreadGuard implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#ifndef THREAD_GUARD_H
#define THREAD_GUARD_H

#include <thread>

class ThreadGuard
{
  std::thread &m_t;

public:
  // explicit: prevent the compiler from using that constructor for implicit
  // conversions
  explicit ThreadGuard(std::thread &t_) : m_t(t_) {}

  ~ThreadGuard()
  {
    if (m_t.joinable())
    {
      m_t.join();
    }
  }

  ThreadGuard(const ThreadGuard &) = delete;
  ThreadGuard &operator=(const ThreadGuard &) = delete;
};

#endif
This post is licensed under CC BY 4.0 by the author.