Appendix B. シグナル

Table of Contents

シグナルハンドラへの接続

gtkmmのウィジットクラスにはシグナルへのアクセッサメソッド(Gtk::Button::signal_clicked()など)があり、これを使えばシグナルをシグナルハンドラに接続することができます。libsigc++というgtkmmで使われるコールバックのためのライブラリは柔軟ですから、シグナルハンドラにはほとんどどんな関数でも設定することができます。しかし、クラスメソッドを使う場合が多いのではないでしょうか。GTK+を使うCプログラマの間では、このようなシグナルハンドラはしばしばコールバックと呼ばれています。

シグナルにつながれるシグナルハンドラの例はこのようになります:

#include <gtkmm/button.h>

void on_button_clicked()
{
    std::cout << "Hello World" << std::endl;
}

main()
{
    Gtk::Button button("Hello World");
    button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
}

このような(関数的でない)コードには考えるべきことがたくさんあります。まずは仕組みを紹介しましょう:

  • シグナルハンドラはon_button_clicked()です。

  • コードはこれをGtk::Buttonオブジェクトであるbuttonにフックしています。

  • このボタンがclickedシグナルを発行すると、on_button_clicked()が呼び出されます。

さて、再び接続をみてみましょう:

    ...
    button.signal_clicked().connect(sigc::ptr_fun(&on_button_clicked));
    ...

注目してほしいのは、コードではon_button_clicked()へのポインタを直接シグナルのconnect()メソッドに渡していない、ということです。そうではなく、sigc::ptr_fun()を呼び、その結果をconnect()に渡しています。

sigc::ptr_fun()sigc::slotオブジェクトを生成します。スロットは見た目も振る舞いも関数に似ていますが、実体はオブジェクトです。こういうものは関数オブジェクトとか、関手(ファンクタ)として知られています。 sigc::ptr_fun()が生成するのは独立した関数か、静的メソッドのためのスロットです。sigc::mem_fun()が生成するのは特定のインスタンスのメンバメソッドのためのスロットです。

次のものはスロットの動作を示す、もうすこし長めのコード例です:

void on_button_clicked();

class some_class
{
    void on_button_clicked();
};

some_class some_object;

main()
{
    Gtk::Button button;
    button.signal_clicked().connect( sigc::ptr_fun(&on_button_clicked) );
    button.signal_clicked().connect( sigc::mem_fun(some_object, &some_class::on_button_clicked) );
}

最初のconnect()の呼び出しは先ほどみたものと全く同じです。新しいものはありません

この次が面白いものです。 sigc::mem_fun()は引数ふたつで呼ばれています。第一引数はsome_objectであり、これは新しく生成するスロットが指し示すオブジェクトです。第二引数はそのオブジェクトのメソッドへのポインタです。この特別なsigc::mem_fun()呼び出しはスロットを生成します。このスロットは、"呼ばれた"ときに、指定したオブジェクトのポインタで渡されたメソッドを呼び出します。この例の場合はsome_object.on_button_clicked()です。

もうひとつ、このコード例で注目してほしいのは、同じシグナルオブジェクトへのconnect()呼び出しを二回行っているところです。これは全く問題ありません。もしもボタンがクリックされれば、ふたつのシグナルハンドラは両方とも呼び出されます。

ここではボタンのclickedシグナルは引数を取らないメソッドを呼び出すようになっています。全てのシグナルに同様のことが求められます。つまり、ふたつの引数を取る関数を引数をとらないシグナルに結びつけることはできません(ただし、sigc::bind()などのアダプタを使えばもちろんこの限りではありません)。ですから、重要なのは、シグナルに接続したいシグナルハンドラがどのような型であるかを知ることです。