TopC++ 入門 › 応用 › デザインパターン

デザインパターン入門(モダン C++ 版)

GoF 23 のうち実務で本当によく出る 8 つを、モダン C++(std::function / shared_ptr / variant)で書き直します。旧来のクラス階層より簡潔です。

生成1. Singleton(一意インスタンス)

「全体で 1 つだけ」のインスタンス。Meyers' Singleton(C++11 以降)が最短・スレッドセーフ。

meyers.cpp
class Logger { public: static Logger& instance() { static Logger inst; // C++11: 初回呼び出し時に 1 度だけ構築(スレッドセーフ) return inst; } void log(const std::string& s); private: Logger() = default; Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; };
使いどころ: ロガー、設定オブジェクト、リソースマネージャ。ただし濫用すると依存関係が隠れて厄介。DI を検討。

生成2. Factory(生成の抽象化)

具体クラスを直接 new せず、生成関数を経由する。階層が変わっても呼び出し元を触らなくて済む。

factory.cpp
struct Shape { virtual double area() const = 0; virtual ~Shape() = default; }; struct Circle : Shape { ... }; struct Square : Shape { ... }; std::unique_ptr<Shape> makeShape(const std::string& kind) { if (kind == "circle") return std::make_unique<Circle>(); if (kind == "square") return std::make_unique<Square>(); throw std::invalid_argument(kind); }
現代的選択: 型の種類が静的に決まるなら std::variant + visit が代替になる。

構造3. Adapter(インターフェース変換)

既存クラスの API を、期待する API に包んで変換する。C の関数を C++ クラスに包むのも広義のこれ。

adapter.cpp
class CPrinter { // 使いたい既存 API public: void printRaw(const char*) { ... } }; class StringPrinter { // 欲しい API CPrinter c_; public: void print(const std::string& s) { c_.printRaw(s.c_str()); } };

構造4. Decorator(機能の積み重ね)

基底インターフェースを同じインターフェースで包み、振る舞いを追加。GUI のスクロールバー + 枠 + 影 みたいな積み重ね。

decorator.cpp
struct Stream { virtual void write(std::string) = 0; virtual ~Stream() = default; }; struct Compress : Stream { std::unique_ptr<Stream> inner; Compress(std::unique_ptr<Stream> s) : inner(std::move(s)) {} void write(std::string s) override { inner->write(compress(s)); } }; // Compress(Encrypt(File)) のような積み重ねが可能

振る舞い5. Strategy(差し替え可能な振る舞い)

アルゴリズムを差し替える。モダン C++ では virtual の代わりに std::function が楽。

strategy.cpp
class Sorter { std::function<bool(int,int)> cmp_; public: Sorter(std::function<bool(int,int)> c) : cmp_(c) {} void run(std::vector<int>& v) { std::sort(v.begin(), v.end(), cmp_); } }; Sorter{[](int a, int b){ return a > b; }}; // 降順戦略

振る舞い6. Observer(イベント通知)

イベント源 → 複数リスナーに通知。std::function のコンテナで簡単に実装できる。

observer.cpp
template<class... Args> class Signal { std::vector<std::function<void(Args...)>> fs_; public: void connect(std::function<void(Args...)> f) { fs_.push_back(std::move(f)); } void emit(Args... a) { for (auto& f : fs_) f(a...); } }; Signal<int> onClick; onClick.connect([](int id){ std::cout << id; }); onClick.emit(42);

振る舞い7. Command(操作のオブジェクト化)

操作を値として持ち回す。undo / redo の基礎。std::function で大部分カバー、複雑なら基底クラス。

command.cpp
struct Command { std::function<void()> exec, undo; }; std::stack<Command> history; history.push({[&]{ x+=1; }, [&]{ x-=1; }}); history.top().exec();

振る舞い8. Visitor(型別分岐)

様々な型に対して処理を切り替える。モダン C++ では std::variant + std::visit が王道。継承+virtual より少ない記述で済む。

visitor.cpp
using Shape = std::variant<Circle, Square, Triangle>; double area(const Shape& s) { return std::visit([](const auto& x){ return x.area(); }, s); } // auto ラムダが C++17 では "generic visitor"
詳細は第 51 回 variant / any、および型消去(cpp-type-erasure)参照。