文字列ベースとファンクタベースの接続の違い

Qtには、C++でシグナルスロット接続を記述するための2つの異なる方法があります:文字列ベースの接続構文とファンクタベースの接続構文です。どちらの構文にも長所と短所があります。以下の表はその違いをまとめたものです。

文字列ベースファンクター・ベース
型チェックはランタイムコンパイル時
暗黙の型変換が可能Y
シグナルをラムダ式に接続できるY
シグナルより多くの引数を持つスロットにシグナルを接続できる(デフォルト・パラメータを使用)Y
C++関数とQML関数を接続することができるY

以下のセクションでは、これらの違いについて詳しく説明し、それぞれの接続構文に特有な機能の使い方を示します。

型チェックと暗黙の型変換

文字列ベースの接続では、実行時に文字列を比較することで型チェックを行います。この方法には3つの制限があります:

  1. 接続エラーはプログラムの実行開始後にしか検出できない。
  2. シグナルとスロットの間で暗黙の変換を行うことができない。
  3. 型定義と名前空間を解決できない。

文字列コンパレータがC++の型情報にアクセスできないため、文字列の正確なマッチングに依存するため、制限2と3が存在します。

対照的に、ファンクタ・ベースの接続はコンパイラによってチェックされます。コンパイラはコンパイル時にエラーを検出し、互換性のある型間の暗黙の変換を可能にし、同じ型の異なる名前を認識します。

例えば、int を伝送するシグナルを、double を受け付けるスロットに接続するには、ファンクタベースの構文しか使用できません。QSliderint の値を保持し、QDoubleSpinBoxdouble の値を保持します。以下のスニペットは、これらを同期させる方法を示している:

    auto slider = new QSlider(this);
    auto doubleSpinBox = new QDoubleSpinBox(this);

    // OK: The compiler can convert an int into a double
    connect(slider, &QSlider::valueChanged,
            doubleSpinBox, &QDoubleSpinBox::setValue);

    // ERROR: The string table doesn't contain conversion information
    connect(slider, SIGNAL(valueChanged(int)),
            doubleSpinBox, SLOT(setValue(double)));

次の例は、名前解決の欠如を示している。QAudioInput::stateChanged()は、"QAudio::State "型の引数で宣言されています。したがって、文字列ベースの接続は、"State" がすでに表示されていても、"QAudio::State" も指定しなければなりません。引数の型は接続の一部ではないため、この問題はファンクターベースの接続には当てはまりません。

    auto audioInput = new QAudioInput(QAudioFormat(), this);
    auto widget = new QWidget(this);

    // OK
    connect(audioInput, SIGNAL(stateChanged(QAudio::State)),
            widget, SLOT(show()));

    // ERROR: The strings "State" and "QAudio::State" don't match
    using namespace QAudio;
    connect(audioInput, SIGNAL(stateChanged(State)),
            widget, SLOT(show()));

    // ...

ラムダ式への接続

ファンクタベースの接続構文では、シグナルを C++11 ラムダ式に接続できます。この機能は、文字列ベースの構文では使用できません。

次の例では、TextSender クラスがQString パラメータを持つtextCompleted() シグナルを発信しています。これがクラス宣言です:

class TextSender : public QWidget {
    Q_OBJECT

    QLineEdit *lineEdit;
    QPushButton *button;

signals:
    void textCompleted(const QString& text) const;

public:
    TextSender(QWidget *parent = nullptr);
};

ユーザーがボタンをクリックすると、TextSender::textCompleted()

TextSender::TextSender(QWidget *parent) : QWidget(parent) {
    lineEdit = new QLineEdit(this);
    button = new QPushButton("Send", this);

    connect(button, &QPushButton::clicked, [=] {
        emit textCompleted(lineEdit->text());
    });

    // ...
}

この例では、QPushButton::clicked() とTextSender::textCompleted() が互換性のないパラメータを持っているにもかかわらず、ラムダ関数によって接続がシンプルになっています。対照的に、文字列ベースの実装では、余計な定型的なコードが必要になります。

注: ファンクタベースの接続構文は、スタンドアロン関数や通常のメンバ関数を含むすべての関数へのポインタを受け付けます。ただし、可読性を高めるため、シグナルはスロット、ラムダ式、その他のシグナルにのみ接続する必要があります。

C++オブジェクトとQMLオブジェクトの接続

文字列ベースの構文では C++ オブジェクトと QML オブジェクトを接続することができますが、 ファンクタベースの構文では接続することができません。なぜなら、QML の型は実行時に解決されるため、C++ コンパイラでは利用できないからです。

次の例では、QMLオブジェクトをクリックするとC++オブジェクトがメッセージを表示し、その逆も同様です。これが QML の型です(QmlGui.qml ):

Rectangle {
    width: 100; height: 100

    signal qmlSignal(string sentMsg)
    function qmlSlot(receivedMsg) {
        console.log("QML received: " + receivedMsg)
    }

    MouseArea {
        anchors.fill: parent
        onClicked: qmlSignal("Hello from QML!")
    }
}

これがC++のクラスです:

class CppGui : public QWidget {
    Q_OBJECT

    QPushButton *button;

signals:
    void cppSignal(const QVariant& sentMsg) const;

public slots:
    void cppSlot(const QString& receivedMsg) const {
        qDebug() << "C++ received:" << receivedMsg;
    }

public:
    CppGui(QWidget *parent = nullptr) : QWidget(parent) {
        button = new QPushButton("Click Me!", this);
        connect(button, &QPushButton::clicked, [=] {
            emit cppSignal("Hello from C++!");
        });
    }
};

シグナルスロットの接続を行うコードです:

    auto cppObj = new CppGui(this);
    auto quickWidget = new QQuickWidget(QUrl("QmlGui.qml"), this);
    auto qmlObj = quickWidget->rootObject();

    // Connect QML signal to C++ slot
    connect(qmlObj, SIGNAL(qmlSignal(QString)),
            cppObj, SLOT(cppSlot(QString)));

    // Connect C++ signal to QML slot
    connect(cppObj, SIGNAL(cppSignal(QVariant)),
            qmlObj, SLOT(qmlSlot(QVariant)));

QVariant 注: QMLのJavaScript関数は、型アノテーションを使用しない限り、すべてvar 型のパラメータをとります。詳しくはQMLメソッドの起動を参照してください。

QPushButton がクリックされると、コンソールは'QML received:「と表示されます。同様に、矩形をクリックすると、コンソールに'C++ received:「と表示されます。

C++ オブジェクトと QML オブジェクトを対話させる他の方法については、C++ から QML オブジェクトを対話させるを参照してください。

スロットのデフォルトパラメータを使って少ないパラメータでシグナルに接続する

通常、接続はスロットの引数の数がシグナルと同じ(あるいはそれ以下)であり、 引数の型がすべて互換性がある場合にのみ可能です。

文字列ベースの接続構文では、このルールを回避することができます。スロットにデフォルトのパラメータがある場合、それらのパラメータをシグナルから省略することができます。スロットにデフォルトのパラメータがある場合、それらのパラメータはシグナルから省略することができます。シグナルがスロットより少ない引数で発行された場合、Qtはデフォルトのパラメータ値を使用してスロットを実行します。

ファンクタベースの接続はこの機能をサポートしていません。

デフォルト引数を持つスロットprintNumber() を持つDemoWidget というクラスがあるとします:

public slots:
    void printNumber(int number = 42) {
        qDebug() << "Lucky number" << number;
    }

文字列ベースの接続を使用すると、DemoWidget::printNumber() は引数を持っていなくても、QApplication::aboutToQuit() に接続することができます。ファンクタ・ベースの接続ではコンパイル・エラーが発生します:

DemoWidget::DemoWidget(QWidget *parent) : QWidget(parent) {

    // OK: printNumber() will be called with a default value of 42
    connect(qApp, SIGNAL(aboutToQuit()),
            this, SLOT(printNumber()));

    // ERROR: Compiler requires compatible arguments
    connect(qApp, &QCoreApplication::aboutToQuit,
            this, &DemoWidget::printNumber);
}

ファンクタベースの構文でこの制限を回避するには、スロットを呼び出すラムダ関数にシグナルを接続します。上記の「ラムダ式への接続」を参照してください。

オーバーロードされたシグナルとスロットの選択

文字列ベースの構文では、パラメータの型を明示的に指定します。その結果、オーバーロードされたシグナルやスロットのインスタンスは明確になります。

対照的に、ファンクターベースの構文では、オーバーロードされたシグナルやスロットは、どのインスタンスを使用するかをコンパイラに伝えるためにキャストする必要があります。

例えば、QLCDNumber には、display() スロットの3つのバージョンがあります:

  1. QLCDNumber::display(int)
  2. QLCDNumber::display(double)
  3. QLCDNumber::display(QString)

int バージョンをQSlider::valueChanged() に接続するには、2つの構文があります:

    auto slider = new QSlider(this);
    auto lcd = new QLCDNumber(this);

    // String-based syntax
    connect(slider, SIGNAL(valueChanged(int)),
            lcd, SLOT(display(int)));

    // Functor-based syntax
    connect(slider, &QSlider::valueChanged,
            lcd, qOverload<int>(&QLCDNumber::display));

qOverload()も参照してください

本書に含まれる文書の著作権は、それぞれの所有者に帰属します 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。