オブザーバパターン用のライブラリとしてC++ではQtのSignal/SlotやBoost Signals2が便利です。QtはGUI作成でよく利用しますが、以下のような理由のためQt依存はできるだけ減らしておきたいです。
- QtのSignal/Slotへ関数を登録するには、汎用関数はslot関数はQtに依存しなければならない
- Qtのライブラリはリンクが大変
そのため、Qt.Signal/SlotではなくBoost Signals2に置き換えたくなる場合があります。Boost Signals2はPImplイディオムで隠蔽できますが、Qt Connect用のSlot関数の宣言は一般的なPImplイディオムでは隠蔽できません。
private slotsを隠蔽できない例
以下、QPushButtonのボタンが押された処理をBoost Signals2を使って書き直したサンプルコードになります。
ポイントは以下の通り。
- signals2へのconnect用関数を用意(connectPressed)
- boost signals2はpImplイディオムで隠蔽
- on_pressed関数はprivate slotsで宣言する必要がある <- 簡単に隠蔽できない
observedpushbutton.h
# include <memory>
# include <functional>
# include <QPushButton>
class ObservedPushButton : public QPushButton
{
Q_OBJECT
public:
ObservedPushButton(QWidget* parent);
~ObservedPushButton();
// Boost Signals2への関数の接続
void connectPressed( std::function<void(void)> func);
private slots:
// Qt connect用のSlot関数
void on_pressed() const;
private:
// Boost Singals2を隠蔽するためのPImplイディオム
struct Impl;
std::unique_ptr<Impl> pImpl;
};
observedpushbutton.cpp
# include "observedpushbutton.h"
# include <boost/signals2/signal.hpp>
struct ObservedPushButton::Impl
{
// Boost signals2はpImplイディオムで隠蔽できる
boost::signals2::signal<void(void)> pressed;
};
ObservedPushButton::ObservedPushButton(QWidget* parent)
: QPushButton(parent)
, pImpl(new ObservedPushButton::Impl())
{
connect(this,SIGNAL(pressed()),this,SLOT(on_pressed()));
}
ObservedPushButton::~ObservedPushButton()
{
}
void
ObservedPushButton::connectPressed(std::function<void(void)> func)
{
pImpl->pressed.connect(func);
}
void
ObservedPushButton::on_pressed()const
{
// Qt connectのSlot関数内でBoost Signals2への関数を呼び出す
pImpl->pressed();
}
private slotsを隠蔽できる例
ポイントは以下のとおり。
- PImpl用クラスはQObject(QtのSlot関数が定義可能なクラス)を継承する
- Qtのコード自動生成の対象になるように、PImpl用クラスの宣言はヘッダファイル上で行う
observedpushbutton.h
# include <memory>
# include <functional>
# include <QPushButton>
class ObservedPushButton : public QPushButton
{
Q_OBJECT
public:
ObservedPushButton(QWidget* parent);
~ObservedPushButton();
// Boost Signals2への関数の接続
void connectPressed( std::function<void(void)> func);
// private slotsを隠蔽!
private:
// Boost Singals2, private slotsを隠蔽するためのPImplイディオム
struct Impl;
std::unique_ptr<Impl> pImpl;
};
observedpushbutton_impl.h
// Qtの自動コード生成の対象になるようにimpl用ヘッダにする
# include "observedpushbutton.h"
# include <boost/signals2/signal.hpp>
// PImpl用クラスはQObjectを継承する
struct ObservedPushButton::Impl : public QObject
{
Q_OBJECT
// Q_OBJECTの宣言直後はprivateになるため、publicを宣言しなおす
public:
boost::signals2::signal<void(void)> pressed;
private slots:
void on_pressed()const;
};
obervedpushbutton.cpp
# include "observedpushbutton_impl.h"
void
ObservedPushButton::Impl::on_pressed()const
{
pressed();
}
ObservedPushButton::ObservedPushButton(QWidget* parent)
: QPushButton(parent)
, pImpl(new ObservedPushButton::Impl())
{
// Slot関数にPImpl用クラスで宣言した関数を使用する
connect(this,SIGNAL(pressed()),pImpl.get(),SLOT(on_pressed()));
}
ObservedPushButton::~ObservedPushButton()
{
}
void
ObservedPushButton::connectPressed(std::function<void(void)> func)
{
pImpl->pressed.connect(func);
}