Qtのprivate slotsを隠蔽する

オブザーバパターン用のライブラリとして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で宣言する必要がある <- 簡単に隠蔽できない

```cpp: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;
};
```

```cpp: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用クラスの宣言はヘッダファイル上で行う

```cpp: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;
};
```

```cpp: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;
};
```

```cpp: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);
}
```