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