The Bridge Pattern

Contents[Show]

The Bridge Pattern is a structural pattern. It decouples the interface from the implementation. In C++, a simplified version is often used: the Pimpl Idiom.

 patterns

Before I write about the Pimpl Idiom, here are the fact about the Bridge Pattern

The Bridge Pattern

Purpose

  • Decouples the interface from the implementation

Also known as

  • Handle/Body
  • Pimpl (pointer to implementation) Idiom

Applicability

  • The interface and the implementation can be extended
  • A change of the interface of the implementation does not affect the client
  • The implementation is hidden

Structure

Bridge

 

Abstraction

  • Defines the interface of the abstraction
  • Has an object of type Implementor

RedefinedAbstraction

  • Implements or refines the interface of the Abstraction

Implementor

  • Defines the interface of the implementation

ConcreteImplementor

  • Implements the interface of the Implementor

The Bridge Pattern has two hierarchies. One hierarchy for the abstraction (interface), and one for the implementation. The client programs against the abstraction, and the abstraction uses the implementation. Consequently, different implementations of the abstraction interface and implementation interface can be used transparently. The Bridge Pattern provides big flexibility because the abstraction and implementation can be varied and exchanged during the program's run time.

The Bridge Pattern is a powerful example exemplifying the combination of inheritance and composition. On one hand, it has two type hierarchies (inheritance); on the other hand, the abstraction has an implementation (composition).

Example

The example shows a straightforward implementation of the Bridge Pattern.

// bridge.cpp

#include <iostream>

class Implementor {                           // (1)
public:
    virtual void implementation() const = 0;

    virtual ~Implementor() = default;
};
 
class ImplementorA: public Implementor {
public:
    ImplementorA() = default;
 
    void implementation() const {
        std::cout << "ImplementatorA::implementation" << '\n';
    }
};
 
class ImplementorB: public Implementor {
public:
    ImplementorB() = default;

    void implementation() const {
        std::cout << "ImplementatorB::implementation" << '\n';
    }
};

class Abstraction {                           // (2)      
public:
    virtual void function() const = 0;
    virtual ~Abstraction() = default;
};

class RefinedAbstraction: public Abstraction {
public:
    RefinedAbstraction(Implementor& impl) : 
		implementor(impl) {
    }
 
    void function() const {
        std::cout << "RefinedAbstraction::function\n";
        implementor.implementation();
    }
private:
    Implementor& implementor;
};
 
int main() {

    std::cout << '\n';

    ImplementorA implementorA;
    ImplementorB implementorB;
 
    RefinedAbstraction refinedAbstraction1(implementorA);  // (3)
    RefinedAbstraction refinedAbstraction2(implementorB);  // (4)

    Abstraction *abstraction1 = &refinedAbstraction1;
    Abstraction *abstraction2 = &refinedAbstraction2;

    abstraction1->function();

    std::cout << '\n';

    abstraction2->function();

    std::cout << '\n';

}

 

The class Implementor (line 1) is the interface for the implementation hierarchy, and the class Abstraction (line 2) is the interface of the abstraction. The instances redefinedAbstraction1 and redefinedAbstraction2 get their implementation in its constructor (lines 3 and 4).

The following screenshot shows the output of the program.

bridgeExample

  • The Adapter Pattern implemented as an object adapter is similar to the Bridge Pattern but has a different intent. The Bridge Pattern's purpose is to separate the interface from the implementation, but the adapter's purpose is to modify an existing interface.
  • The Abstract Factory can create and configure a Bridge.

In C++, a simplified version of the Bridge Pattern is often used.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Stay informed about my mentoring programs. Subscribe for the news.

 

 

The Pimpl Idiom

The key idea of the Pimpl Idiom is that the implementation of the class is hidden behind a pointer.

Here is a recipe for implementing the Pimpl Idiom:

  1. Move private data and member functions of the class (public class) to a separate class (pimpl class).
  2. Forward declare the pImpl class in the header of the public class.
  3. Declare pointer of type pimpl class in the public class.
  4. Define the pimpl class in the source file of the public class.
  5. Instantiate the pimpl class in the constructor of the public class.
  6. Member functions of the public class use those of the pimpl class.

Bartlomiej Filipek provides in his blog post "The Pimpl Pattern - what you should know" a nice example of the Pimpl Idiom:

// class.h
class MyClassImpl;
class MyClass
{
public:
    explicit MyClass();
    ~MyClass(); 

    // movable:
    MyClass(MyClass && rhs) noexcept;                          // (2)
    MyClass& operator=(MyClass && rhs) noexcept;               // (3)

    // and copyable
    MyClass(const MyClass& rhs);                               // (4)
    MyClass& operator=(const MyClass& rhs);                    // (5)

    void DoSth();
    void DoConst() const;

private:
    const MyClassImpl* Pimpl() const { return m_pImpl.get(); }  // (6)
    MyClassImpl* Pimpl() { return m_pImpl.get(); }              // (7)

    std::unique_ptr<MyClassImpl> m_pImpl;                       // (1)
};

// class.cpp
class MyClassImpl
{
public:
    ~MyClassImpl() = default;

    void DoSth() { }
    void DoConst() const { }
};

MyClass::MyClass() : m_pImpl(new MyClassImpl()) 
{

}

MyClass::~MyClass() = default;
MyClass::MyClass(MyClass &&) noexcept = default;
MyClass& MyClass::operator=(MyClass &&) noexcept = default;

MyClass::MyClass(const MyClass& rhs)
    : m_pImpl(new MyClassImpl(*rhs.m_pImpl))
{}

MyClass& MyClass::operator=(const MyClass& rhs) {
    if (this != &rhs) 
        m_pImpl.reset(new MyClassImpl(*rhs.m_pImpl));

    return *this;
}

void MyClass::DoSth()
{
    Pimpl()->DoSth();
}

void MyClass::DoConst() const
{
    Pimpl()->DoConst();
}

 

Here are the key ideas of his implementation. I added a few line markers:

  • The pimpl is a std::unique_ptr<MyClassImpl> (line 1)
  • The class support copy semantics and move semantics (lines 2 - 5)
  • The private Pimp() member functions of MyClass return a const and a non-const MyClassImpl pointers (lines 6 and 7)

You may ask yourself. What are the benefits of the Pimpl Idiom? It would be easier to merge the implementation MyClassImpl into the abstraction MyClass.

Pros and Cons

Let me start with the pros

Pros

  • Binary Compatibility: When you change the implementation, this would not break the interface for the client using the abstraction.
  • Compile Time: Changing the implementation does not require a recompilation of the client using the abstraction. Due to this fact, the Pimpl Idiom is often called a compilation firewall. This benefit is obsolete with modules in C++20
  • Extensibility: It is pretty easy to exchange the implementation during run time. In general, there is no need for virtuality.

Cons

  • Performance: The pointer indirection causes extra run-time costs.
  • The Bix Six: You have to think about the Big Six (Read my post previous post the "C++ Core Guidelines: The Rule of Zero, Five, and Six" to get more details about the Big Six.). In particular, due to the fact that the abstraction has a std::unique_ptr, it supports no copy semantics. This means in the concrete case.
    • You have to implement copy semantics if you need it.
    • When you implement copy semantics, you don't automatically get move semantics and a default constructor.
  • Memory Allocation: The Pimpl Idiom requires a memory allocation. This memory allocation may not be possible in embedded systems and may cause memory fragmentation.

What's Next?

Additionally, the Decorator Pattern is an often-used structural pattern from the book "Design Patterns: Elements of Reusable Object-Oriented Software". Its job is to extend an object with responsibilities dynamically. Let me present the Decorator in my next post.

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, and Phillip Diekmann.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, Ralf Abramowitsch, John Nebel, Mipko, and Alicja Kaminska.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

 

 

 

 

Comments   

0 #1 Marius Mikucionis 2022-10-17 06:46
Thank you for this nice description.
The implementation of move semantics can be simplified and inlined (saving extra function calls and header!) by avoiding the use of unique_ptr and using a simple implementation instead:

MyClass::MyClass(MyClass&& other) noexcept { *this = std::move(other); }

MyClass& MyClass::operator(MyClass&& other) noexcept { std::swap(m_pImpl, other.m_pImpl); }

Granted, it goes against DRY principle (reusing unique_ptr implementation), but even with unique_ptr we have repeating boilerplate (repeating the move semantics signatures in both header and cpp file).
Quote
0 #2 Marius Mikucionis 2022-10-17 06:51
There is a small typo in the article: Bridget Pattern :-)
Quote

Mentoring

Stay Informed about my Mentoring

 

English Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 976

Yesterday 6352

Week 25902

Month 46649

All 10968110

Currently are 169 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments