LoginSignup
1
1

More than 5 years have passed since last update.

Qtのprivate slotsを隠蔽する

Last updated at Posted at 2016-11-02

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1