C++
Qt

Qtのprivate slotsを隠蔽する

More than 1 year has passed since last update.

オブザーバパターン用のライブラリとして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);
}