このページでは

シグナルとスロット

はじめに

GUIプログラミングにおいて、あるウィジェットを変更した際に、別のウィジェットにその変更を通知させたい場合がよくあります。より一般的には、あらゆる種類のオブジェクト同士が相互に通信できるようにしたいものです。例えば、ユーザーがClose ボタンをクリックした場合、そのウィンドウのclose()関数が呼び出されるようにしたいでしょう。

他のツールキットでは、コールバックを使用してこの種の通信を実現しています。コールバックとは関数へのポインタのことです。したがって、処理関数に何らかのイベントを通知させたい場合は、別の関数(コールバック)へのポインタをその処理関数に渡します。 その後、処理関数は適切なタイミングでコールバックを呼び出します。この手法を採用して成功しているフレームワークも存在しますが、コールバックは直感的ではなく、コールバック引数の型の一貫性を確保する上で問題が生じる可能性があります。

シグナルとスロット

Qtでは、コールバック手法に代わる方法として、シグナルとスロットを使用します。シグナルは、特定のイベントが発生した際に発信されます。Qt Widgetsには多くの定義済みシグナルがありますが、Widgetsをサブクラス化することで、独自のシグナルを追加することも可能です。 スロットとは、特定のシグナルに応答して呼び出される関数のことです。Qt Widgetsには多くの定義済みスロットがありますが、関心のあるシグナルを処理できるように、ウィジェットをサブクラス化して独自のスロットを追加するのが一般的な手法です。

オブジェクト間の関連付け

シグナルとスロットの仕組みは型安全です。シグナルのシグネチャは、それを受け取るスロットのシグネチャと一致しなければなりません。(実際、スロットは余分な引数を無視できるため、受け取るシグナルよりも短いシグネチャを持つ場合があります。)シグネチャが互換性があるため、関数ポインタベースの構文を使用する際、コンパイラが型の不一致を検出するのに役立ちます。 文字列ベースのSIGNALおよびSLOT構文では、実行時に型の不一致が検出されます。シグナルとスロットは疎結合です。シグナルを発行するクラスは、どのスロットがシグナルを受信するかを認識せず、また気にも留めません。 Qtのシグナルとスロットのメカニズムにより、シグナルをスロットに接続すると、適切なタイミングでそのシグナルのパラメータが渡されてスロットが呼び出されることが保証されます。シグナルとスロットは、任意の型の引数を任意の個数受け取ることができます。これらは完全に型安全です。

QObject またはそのサブクラス(例:QWidget )を継承するすべてのクラスは、シグナルとスロットを含むことができます。 シグナルは、オブジェクトが他のオブジェクトにとって興味深い状態変化を起こした際に、そのオブジェクトによって発信されます。これが、オブジェクトが行う唯一の通信手段です。オブジェクトは、自分が発信したシグナルを何かが受信しているかどうかを知りませんし、気にもしません。これこそが真の情報カプセル化であり、オブジェクトがソフトウェアコンポーネントとして確実に利用できるようにします。

スロットはシグナルの受信に使用できますが、それ自体は通常のメンバ関数でもあります。オブジェクトが自身のシグナルを受信しているかどうかを知らないのと同様に、スロットも自分にシグナルが接続されているかどうかを知りません。これにより、Qt を使用して真に独立したコンポーネントを作成することが可能になります。

1つのスロットには任意の数のシグナルを接続でき、1つのシグナルも必要な数のスロットに接続できます。シグナルを別のシグナルに直接接続することさえ可能です(これにより、最初のシグナルが発火するたびに、2番目のシグナルが直ちに発火します)。

シグナルとスロットが組み合わさることで、強力なコンポーネントプログラミングの仕組みが構成されます。

シグナル

シグナルは、オブジェクトの内部状態が、そのクライアントや所有者にとって関心のあるような形で変化した際に、オブジェクトから発せられます。シグナルはパブリックアクセス関数であり、どこからでも発せることができますが、シグナルを定義しているクラスおよびそのサブクラスからのみ発せることが推奨されます。

シグナルが発せられると、通常、それに接続されたスロットは、通常の関数呼び出しと同様に即座に実行されます。この際、シグナルとスロットの仕組みは、GUIのイベントループとは完全に独立しています。emit 文に続くコードの実行は、すべてのスロットが戻り値を返した後に行われます。queued connections を使用する場合は状況が若干異なります。この場合、emit キーワードに続くコードは直ちに実行され、スロットは後で実行されます。

1つのシグナルに複数のスロットが接続されている場合、シグナルが発火した際に、接続された順序に従ってスロットが順次実行されます。

シグナルはmocによって自動的に生成されるため、.cpp ファイル内で実装してはなりません。

引数に関する注意点:これまでの経験上、シグナルやスロットは、特殊な型を使用しない方が再利用性が高くなります。もしQScrollBar::valueChanged()が、仮にQScrollBar::Rangeのような特殊な型を使用していた場合、QScrollBar 用に特別に設計されたスロットにしか接続できなくなってしまいます。異なる入力ウィジェット同士を接続することは不可能になってしまうでしょう。

スロット

スロットは、それに接続されたシグナルが発火したときに呼び出されます。スロットは通常のC++関数であり、通常通り呼び出すことができます。唯一の特別な点は、シグナルを接続できることです。

スロットは通常のメンバ関数であるため、直接呼び出される場合は通常のC++のルールに従います。 しかし、スロットとして、アクセスレベルに関係なく、どのコンポーネントからもシグナル・スロット接続を介して呼び出すことができます。つまり、任意のクラスのインスタンスから発せられたシグナルによって、無関係なクラスのインスタンス内のプライベートなスロットが呼び出される可能性があります。

また、スロットを仮想関数として定義することも可能であり、これは実用上非常に有用であることがわかっています。

コールバックと比較すると、シグナルとスロットは、その柔軟性の高さゆえにわずかに処理速度が低下しますが、実際のアプリケーションではその差はごくわずかです。一般的に、いくつかのスロットに接続されているシグナルを発行することは、非仮想関数呼び出しを用いてレシーバーを直接呼び出す場合よりも、およそ 10 倍遅くなります。 これは、接続オブジェクトを特定し、すべての接続を安全に反復処理し(つまり、シグナルの発信中に後続のレシーバーが破棄されていないことを確認し)、パラメータを汎用的な方法でマーシャリングするために必要なオーバーヘッドです。 10回の非仮想関数呼び出しは多いように聞こえるかもしれませんが、例えばnew やdelete といった操作に比べれば、はるかにオーバーヘッドは少ないです。裏側でnew やdelete を必要とする文字列、ベクトル、またはリストの操作を行うと、シグナルとスロットのオーバーヘッドは、関数呼び出し全体のコストのごく一部に過ぎません。 これは、スロット内でシステムコールを行う場合や、間接的に10個以上の関数を呼び出す場合にも同様です。シグナルとスロットのメカニズムが持つシンプルさと柔軟性は、そのオーバーヘッドを十分に補う価値があり、ユーザーにはそのオーバーヘッドに気づかれることさえありません。

なお、signals やslots という名前の変数を定義している他のライブラリを、Qtベースのアプリケーションと一緒にコンパイルすると、コンパイラの警告やエラーが発生する可能性があります。この問題を解決するには、問題となっているプリプロセッサシンボルを#undef で無効にしてください。

簡単な例

最小限の C++ クラス宣言は、次のようなものになります:

class Counter
{
public:
    Counter() { m_value = 0; }

    int value() const { return m_value; }
    void setValue(int value);

private:
    int m_value;
};

QObject を基にした簡単なクラスは、次のようなものになるでしょう:

#include <QObject>

class Counter : public QObject
{
    Q_OBJECT

// Note. The Q_OBJECT macro starts a private section.
// To declare public members, use the 'public:' access modifier.
public:
    Counter() { m_value = 0; }

    int value() const { return m_value; }

public slots:
    void setValue(int value);

signals:
    void valueChanged(int newValue);

private:
    int m_value;
};

QObject を基にしたバージョンは、同じ内部状態を持ち、その状態にアクセスするためのパブリックメソッドを提供しますが、さらにシグナルとスロットを用いたコンポーネントプログラミングのサポートも備えています。このクラスは、シグナル `valueChanged()` を発信することで、その状態が変化したことを外部に通知することができ、また、他のオブジェクトがシグナルを送信できるスロットも持っています。

シグナルまたはスロットを含むすべてのクラスは、宣言の先頭にQ_OBJECT を明記する必要があります。また、QObject を(直接的または間接的に)継承している必要があります。

スロットはアプリケーションプログラマによって実装されます。以下は、Counter::setValue() のスロットの1つの実装例です:

void Counter::setValue(int value)
{
    if (value != m_value) {
        m_value = value;
        emit valueChanged(value);
    }
}

emit 行は、新しい値を引数として、オブジェクトからvalueChanged() シグナルを発行します。

以下のコードスニペットでは、2つのCounter オブジェクトを作成し、QObject::connect() を使用して、1つ目のオブジェクトのvalueChanged() シグナルを2つ目のオブジェクトのsetValue() スロットに接続しています:

    Counter a, b;
    QObject::connect(&a, &Counter::valueChanged,
                     &b, &Counter::setValue);

    a.setValue(12);     // a.value() == 12, b.value() == 12
    b.setValue(48);     // a.value() == 12, b.value() == 48

a.setValue(12) を呼び出すと、a がvalueChanged(12) シグナルを発行し、b はそのsetValue() スロットでこれを受信します。つまり、b.setValue(12) が呼び出されます。その後、b も同様のvalueChanged() シグナルを発行しますが、b のvalueChanged() シグナルにはスロットが接続されていないため、このシグナルは無視されます。

なお、setValue() 関数は、value != m_value の場合にのみ値を設定し、シグナルを発生させます。これにより、循環的な接続(例:b.valueChanged() がa.setValue() に接続されている場合など)における無限ループが防止されます。

デフォルトでは、接続を確立するたびにシグナルが1つ発火し、重複した接続の場合は2つのシグナルが発火します。disconnect() を1回呼び出すだけで、これらすべての接続を解除できます。Qt::UniqueConnection にtype を渡した場合、その接続が重複していない場合にのみ接続が確立されます。すでに重複(同じオブジェクト上のまったく同じスロットへのまったく同じシグナル)が存在する場合、接続は失敗し、connectはfalse を返します。

この例は、オブジェクトが互いに関する情報を一切知らなくても連携できることを示しています。これを実現するには、オブジェクト同士を接続するだけでよく、これはいくつかの単純なQObject::connect()関数の呼び出し、あるいはu icの自動接続機能によって達成できます。

実際の例

以下は、メンバ関数を持たない単純なウィジェットクラスのヘッダーの例です。これは、独自のアプリケーションでシグナルとスロットをどのように活用できるかを示すことを目的としています。

#ifndef LCDNUMBER_H
#define LCDNUMBER_H

#include <QFrame>

class LcdNumber : public QFrame
{
    Q_OBJECT

LcdNumber このクラスは、シグナル・スロットに関する知識の大部分を持つQObject を、QFrame およびQWidget を介して継承しています。これは、組み込みのQLCDNumber ウィジェットと多少似ています。

Q_OBJECT マクロは、プリプロセッサによって展開され、moc によって実装されるいくつかのメンバ関数を宣言します。「LcdNumber の vtable への参照が未定義」といった内容のコンパイラエラーが発生した場合は、おそらくmoc を実行し忘れたか、リンクコマンドに moc の出力を含めるのを忘れている可能性があります。

public:
    LcdNumber(QWidget *parent = nullptr);

signals:
    void overflow();

クラスのコンストラクタとpublic メンバーの後に、signals クラスを宣言します。LcdNumber クラスは、表示不可能な値を表示するよう要求された際に、overflow() というシグナルを発します。

オーバーフローを気にしない場合や、オーバーフローが発生しないことが分かっている場合は、overflow() シグナルを無視しても構いません。つまり、どのスロットにも接続しないようにします。

一方、数値がオーバーフローした際に2つの異なるエラー関数を呼び出したい場合は、そのシグナルを2つの異なるスロットに接続するだけで済みます。Qtは(接続された順序で)両方の関数を呼び出します。

public slots:
    void display(int num);
    void display(double num);
    void display(const QString &str);
    void setHexMode();
    void setDecMode();
    void setOctMode();
    void setBinMode();
    void setSmallDecimalPoint(bool point);
};

#endif

スロットとは、他のウィジェットの状態変化に関する情報を取得するために使用される受信関数です。上記のコードが示すように、LcdNumber は、表示される数値を設定するためにスロットを使用しています。display() は、クラスとプログラムの他の部分とのインターフェースの一部であるため、このスロットはパブリックになっています。

いくつかのサンプルプログラムでは、QScrollBar のvalueChanged()シグナルをdisplay() スロットに接続しており、これによりLCD上の数字にスクロールバーの値が継続的に表示されます。

なお、display() はオーバーロードされている点に注意してください。Qt のシグナルとスロットは、強型付けされた接続を実現します。これらは、従来のコールバックよりもコンパイル時に安全です。コールバックの場合、異なる関数名が必要となり、手動で型を追跡する必要があります。一方、オーバーロードされた関数では、どのバージョンを使用するかを指定する必要があります。その方法については、次のセクションで説明します。

オーバーロードされたシグナルおよびスロットへの接続

シグナルやスロットがオーバーロードされている場合(異なるパラメータを持つ複数のバージョンが存在する場合)、関数ポインタの構文を使用して、接続したいバージョンを明示的に指定する必要があります。曖昧さを解消するために、qOverload() またはstatic_cast を使用できます。

// Connect to the QString overload of QCompleter::activated(const QString &)
connect(completer, qOverload<const QString &>(&QCompleter::activated),
        this, &MyClass::onActivatedText);

// Or select QLCDNumber::display(int) when connecting from QSlider::valueChanged(int)
connect(slider, &QSlider::valueChanged,
        lcd, qOverload<int>(&QLCDNumber::display));

// Using static_cast (more verbose):
connect(completer, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::activated),
        this, &MyClass::onActivatedText);

// Or using a lambda to call the correct overload:
connect(slider, &QSlider::valueChanged,
        this, [lcd](int value) { lcd->display(value); });

自動接続管理

Qt は、QObject から派生した型間の接続のライフタイムを自動的に管理します。送信側または受信側のオブジェクトのいずれかが破棄されると、接続は自動的に解除され、削除されたオブジェクトへの呼び出しが防止されます。これは、関数ポインタ構文と文字列ベースの SIGNAL/SLOT 構文の両方に適用されます。

ラムダ接続の場合、コンテキストオブジェクト(通常は `this`)を指定することで、コンテキストが破棄された際にラムダが確実に切断されるようにします:

connect(button, &QPushButton::clicked, this, [this]{ handleClick(); });

Qt XMLは完全に破棄されたオブジェクトへのシグナルの配信を防止しますが、派生クラスのデストラクタが終了した後、~QObject に到達する前のオブジェクト破棄中に、シグナルが配信される可能性があります。この制限は、特に関数ポインタ構文で作成された接続に適用されます。これは、基底クラスのデストラクタが、すでに破棄された派生クラスが接続されていたシグナルを発生させた場合に発生する可能性があります。 これがクラスにとって問題となる可能性がある場合は、デストラクタ内でそのようなシグナルを明示的に切断することを検討してください。

デフォルト引数を持つシグナルとスロット

シグナルおよびスロットのシグネチャには引数が含まれることがあり、それらの引数にはデフォルト値を設定できます。QObject::destroyed() を例に考えてみましょう:

void destroyed(QObject* = nullptr);

QObject が削除されると、このQObject::destroyed()シグナルが発信されます。削除されたQObject へのダングリング参照が存在する可能性がある場所すべてで、このシグナルをキャッチし、クリーンアップできるようにしたいとします。適切なスロットのシグネチャは、次のようなものになるでしょう:

void objectDestroyed(QObject* obj = nullptr);

シグナルをスロットに接続するには、QObject::connect() を使用します。シグナルとスロットを接続する方法はいくつかあります。1つ目は、関数ポインタを使用する方法です:

connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);

関数ポインタとともにQObject::connect()を使用することには、いくつかの利点があります。まず、コンパイラがシグナルの引数がスロットの引数と互換性があるかどうかをチェックできるようになります。また、必要に応じて、引数はコンパイラによって暗黙的に変換されることもあります。

ファンクタやC++11のラムダ式を使用して接続することもできます:

connect(sender, &QObject::destroyed, this, [=](){ this->m_objects.remove(sender); });

これらのケースのいずれにおいても、`connect()` の呼び出しにおいて `this ` をコンテキストとして指定します。コンテキストオブジェクトは、レシーバーがどのスレッドで実行されるべきかに関する情報を提供します。コンテキストを指定することで、レシーバーがコンテキストスレッドで確実に実行されるため、これは重要な点です。

送信者またはコンテキストが破棄されると、ラムダは切断されます。シグナルが発信される時点で、ファンクタ内で使用されているオブジェクトがすべて存続しているように注意する必要があります。

シグナルをスロットに接続するもう一つの方法は、QObject::connect() およびSIGNAL とSLOT マクロを使用することです。引数にデフォルト値がある場合、SIGNAL() およびSLOT() マクロに引数を含めるかどうかのルールは、SIGNAL() マクロに渡されるシグネチャの引数数が、SLOT() マクロに渡されるシグネチャの引数数よりも少なくあってはならない、というものです。

以下のすべてが動作します:

connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));

しかし、次のコードは動作しません:

connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));

…なぜなら、スロットはQObject を期待しているが、シグナルはそれを送信しないからです。この接続では、実行時エラーが発生します。

このQObject::connect()のオーバーロードを使用する場合、シグナルとスロットの引数はコンパイラによってチェックされないことに注意してください。

シグナルとスロットの高度な使い方

シグナルの送信元に関する情報が必要な場合のために、Qt XML では `QObject::sender()` 関数を用意しています。この関数は、シグナルを送信したオブジェクトへのポインタを返します。

ラムダ式は、スロットにカスタム引数を渡すための便利な方法です:

connect(action, &QAction::triggered, engine,
        [=]() { engine->processAction(action->text()); });

サードパーティ製のシグナルおよびスロットとQtの併用

Qtをサードパーティ製のシグナル/スロット機構と併用することは可能です。同じプロジェクト内で両方の機構を併用することもできます。そのためには、CMakeプロジェクトファイルに以下のように記述してください:

target_compile_definitions(my_app PRIVATE QT_NO_KEYWORDS)

qmake プロジェクト (.pro) ファイルには、次のように記述する必要があります:

CONFIG += no_keywords

これは、signals 、slots 、emit というmocキーワードをQtが定義しないように指示するものです。これらの名前は、Boostなどのサードパーティ製ライブラリで使用されるためです。 その後、no_keywords フラグを指定したままQtのシグナルとスロットを引き続き使用するには、ソースコード内のQt mocキーワードをすべて、対応するQtマクロQ_SIGNALS (またはQ_SIGNAL )、Q_SLOTS (またはQ_SLOT )、およびQ_EMIT に置き換えるだけで済みます。

Qtベースのライブラリにおけるシグナルとスロット

QtベースのライブラリのパブリックAPIでは、signals やslots の代わりに、Q_SIGNALS およびQ_SLOTS というキーワードを使用する必要があります。そうしないと、QT_NO_KEYWORDS を定義しているプロジェクトで、そのライブラリを使用することが困難になります。

この制限を強制するために、ライブラリの作成者は、ライブラリをビルドする際にプリプロセッサ定義QT_NO_SIGNALS_SLOTS_KEYWORDS を設定することができます。

この定義により、ライブラリの実装で他の Qt 固有のキーワードが使用できるかどうかに影響を与えることなく、シグナルとスロットが除外されます。

QLCDNumber 、QObject::connect()、メタオブジェクトシステム、およびQt のプロパティシステムも参照してください 。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.