文字列ベースとファンクタベースの接続の違い
Qtには、C++でシグナルスロット接続を記述するための2つの異なる方法があります:文字列ベースの接続構文とファンクタベースの接続構文です。どちらの構文にも長所と短所があります。以下の表はその違いをまとめたものです。
文字列ベース | ファンクター・ベース | |
---|---|---|
型チェックは | ランタイム | コンパイル時 |
暗黙の型変換が可能 | Y | |
シグナルをラムダ式に接続できる | Y | |
シグナルより多くの引数を持つスロットにシグナルを接続できる(デフォルト・パラメータを使用) | Y | |
C++関数とQML関数を接続することができる | Y |
以下のセクションでは、これらの違いについて詳しく説明し、それぞれの接続構文に特有な機能の使い方を示します。
型チェックと暗黙の型変換
文字列ベースの接続では、実行時に文字列を比較することで型チェックを行います。この方法には3つの制限があります:
- 接続エラーはプログラムの実行開始後にしか検出できない。
- シグナルとスロットの間で暗黙の変換を行うことができない。
- 型定義と名前空間を解決できない。
文字列コンパレータがC++の型情報にアクセスできないため、文字列の正確なマッチングに依存するため、制限2と3が存在します。
対照的に、ファンクタ・ベースの接続はコンパイラによってチェックされます。コンパイラはコンパイル時にエラーを検出し、互換性のある型間の暗黙の変換を可能にし、同じ型の異なる名前を認識します。
例えば、int
を伝送するシグナルを、double
を受け付けるスロットに接続するには、ファンクタベースの構文しか使用できません。QSlider はint
の値を保持し、QDoubleSpinBox はdouble
の値を保持します。以下のスニペットは、これらを同期させる方法を示している:
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つのバージョンがあります:
QLCDNumber::display(int)
QLCDNumber::display(double)
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.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。