2023年4月25日 星期二

C++:簡介 Bridge 模式與 Pimpl

本文為 C++ Software Design 書的第 28 節內容。 

 

Bridge pattern 的目的是將 implementation 與 abstraction 盡可能分離開來。舉個例子:

class ElectricEngine {
};
class ElectricCar {
public:
  ElectricCar() : engine_{ /*ElectricEngine params*/ } { ... };
private:
  ElectricEngine engine_;
};
上述例子中當 ElectricEngine 改變時,ElectricCar 也得跟著改變。使用 forward declaration 與指標為解決這個問題的一種方法:

//--- ElectricCar.h ---
struct ElectricEngine; // Forward declaration

class ElectricCar {
private:
  std::unique_ptr<ElectricEngine> engine_;
};

//--- ElectricCar.cpp ---
#include "ElectricEngine.h" // Include the real header here!
ElectricCar::ElectricCar() : engine_{ std::make_unique(...) }
而 Bridge pattern 則是將 Engine 再加一層 abstraction 讓引擎可以有多種實作方式:

//--- Engine.h ---
class Engine {
};

//--- ElectricCar.h ---
#include "Engine.h"

class ElectricCar {
private:
  std::unique_ptr<ElectricEngine> engine_;
};

//--- ElectricEngine.h ---
#include "Engine.h"
class ElectricEngine : public Engine {
};

//--- ElectricCar.cpp ---
#include "ElectricCar.h"
#include "ElectricEngine.h"
ElectricCar::ElectricCar() : engine_{ std::make_unique<ElectricEngine>(...) }
如此一來就能將需要 dependency 降到最低。 Pointer-to-implementation (Pimpl) 是實作 bridge pattern 的一種方法。雖然概念簡單但其中有不少細節:

//--- Person.h ---
class Person {
public:
  Person();
  // Rule of 5
  ~Person();
  Person(Person const& other);
  Person& operator=(Person const& other);
  Person(Person&& other);
  Person& operator=(Person&& other);
private:
  struct Impl;
  std::unique_ptr<Impl> const pimpl_;
};

//--- Person.cpp ---
#include "Person.h"
struct Person::Impl {
  //...
};

Person::Person() : pimpl_{std::make_unique<Impl>()} {...}
Person::~Person() = default;

Person::Person(Person const& other)
  : pimpl_{std::make_unique<Impl>(*other.pimpl_)} {...}
Person& operator=(Person const& other) {
  *pimpl_ = *other.pimpl_;
  return *this;
}

Person::Person(Person&& other)
  : pimpl_{std::make_unique<Impl>(std::move(*other.pimpl_))} {...}
Person& operator=(Person&& other) {
  *pimpl_ = std::move(*other.pimpl_);
  return *this;
}
最重要的細節是 destructor 必須 declare 在 Person.h,但是得 define 在 Person.cpp。因為 pimpl_ 內的類別為在 Person.h 中沒有 definition 的 Impl,在 Person.cpp 中加入 default destructor (而不是寫在 Person.h)即可。由於我們自己寫了 destructor,我們同時得符合 rule of 5。

沒有留言:

張貼留言