Sinyal dan Slot
Sinyal dan slot digunakan untuk komunikasi antar objek. Mekanisme sinyal/slot adalah fitur sentral Qt dan mungkin bagian yang paling berbeda dari toolkit lainnya.
Dalam pemrograman GUI, kita sering ingin perubahan di satu widget diberitahukan ke widget lain. Secara lebih umum, kita ingin objek dari jenis apa pun dapat berkomunikasi satu sama lain. Misalnya, jika kita mengurai file XML, kita mungkin ingin memberi tahu tampilan daftar yang mewakili struktur file XML setiap kali menemukan tag baru.
Toolkit lama mencapai komunikasi semacam ini menggunakan callback. Callback adalah pointer ke fungsi, jadi jika Anda ingin fungsi pemrosesan memberi tahu Anda tentang suatu peristiwa, Anda meneruskan pointer ke fungsi lain (callback) ke fungsi pemrosesan. Fungsi pemrosesan kemudian memanggil callback pada waktu yang tepat. Callback memiliki dua kelemahan mendasar. Pertama, mereka tidak aman tipe. Kita tidak pernah bisa yakin bahwa fungsi pemrosesan akan memanggil callback dengan argumen yang benar. Kedua, callback sangat terikat dengan fungsi pemrosesan karena fungsi pemrosesan harus tahu callback mana yang akan dipanggil.
Di Qt, kami memiliki alternatif untuk teknik callback. Kami menggunakan sinyal dan slot. Sinyal dipancarkan saat peristiwa tertentu terjadi. Widget Qt memiliki banyak sinyal yang telah ditentukan sebelumnya, tetapi kita selalu dapat membuat subkelas untuk menambahkan sinyal kita sendiri. Slot adalah fungsi yang dipanggil sebagai respons terhadap sinyal tertentu. Widget Qt memiliki banyak slot yang telah ditentukan sebelumnya, tetapi sudah menjadi praktik umum untuk menambahkan slot Anda sendiri sehingga Anda dapat menangani sinyal yang Anda minati.
Mekanisme sinyal dan slot aman tipe: tanda tangan sinyal harus cocok dengan tanda tangan slot yang menerima. (Faktanya, sebuah slot mungkin memiliki tanda tangan yang lebih pendek daripada sinyal yang diterimanya karena dapat mengabaikan argumen tambahan.) Karena tanda tangan kompatibel, kompiler dapat membantu kita mendeteksi ketidakcocokan tipe. Sinyal dan slot terikat secara longgar: kelas yang memancarkan sinyal tidak tahu dan tidak peduli slot mana yang menerima sinyal tersebut. Mekanisme sinyal dan slot Qt memastikan bahwa jika Anda menghubungkan sinyal ke slot, slot akan dipanggil dengan parameter sinyal pada waktu yang tepat. Sinyal dan slot dapat mengambil sejumlah argumen dari jenis apa pun. Mereka sepenuhnya aman tipe: tidak ada lagi core dump callback!
Semua kelas yang mewarisi QObject atau salah satu subkelasnya (misalnya QWidget) dapat berisi sinyal dan slot. Sinyal dipancarkan oleh objek ketika mereka mengubah statusnya dengan cara yang mungkin menarik bagi dunia luar. Ini adalah semua yang dilakukan objek untuk berkomunikasi. Objek tidak tahu dan tidak peduli apakah ada yang menerima sinyal yang dipancarkannya. Ini adalah enkapsulasi informasi yang benar, dan memastikan bahwa objek dapat digunakan sebagai komponen perangkat lunak.
Slot dapat digunakan untuk menerima sinyal, tetapi mereka juga merupakan fungsi anggota normal. Sama seperti objek tidak tahu apakah ada yang menerima sinyalnya, slot tidak tahu apakah ada sinyal yang terhubung dengannya. Ini memastikan bahwa komponen yang benar-benar independen dapat dibuat dengan Qt.
Anda dapat menghubungkan sebanyak mungkin sinyal ke satu slot, dan satu sinyal dapat dihubungkan ke sebanyak mungkin slot yang Anda inginkan. Bahkan dimungkinkan untuk menghubungkan sinyal langsung ke sinyal lain. (Ini akan memancarkan sinyal kedua segera setelah sinyal pertama dipancarkan.)
Bersama-sama, sinyal dan slot membentuk mekanisme pemrograman komponen yang kuat.
Contoh Kecil
Deklarasi kelas C++ minimal dapat berupa:
class Foo { public: Foo(); int value() const { return val; } void setValue( int ); private: int val; };Kelas Qt kecil dapat berupa:
class Foo : public QObject { Q_OBJECT public: Foo(); int value() const { return val; } public slots: void setValue( int ); signals: void valueChanged( int ); private: int val; };Kelas ini memiliki status internal yang sama, dan metode publik untuk mengakses status, tetapi selain itu memiliki dukungan untuk pemrograman komponen menggunakan sinyal dan slot: kelas ini dapat memberi tahu dunia luar bahwa statusnya telah berubah dengan memancarkan sinyal, valueChanged(), dan memiliki slot yang dapat dikirimi sinyal oleh objek lain.
Semua kelas yang berisi sinyal atau slot harus menyebutkan Q_OBJECT dalam deklarasinya.
Slot diimplementasikan oleh programmer aplikasi. Berikut adalah kemungkinan implementasi Foo::setValue():
void Foo::setValue( int v ) { if ( v != val ) { val = v; emit valueChanged(v); } }Baris emit valueChanged(v) memancarkan sinyal valueChanged dari objek. Seperti yang Anda lihat, Anda memancarkan sinyal dengan menggunakan emit signal(arguments).
Berikut adalah salah satu cara untuk menghubungkan dua objek ini bersama:
Foo a, b; connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int))); bue( 11 ); // a == undefined b == 11 aue( 79 ); // a == 79 b == 79 b(); // returns 79 Memanggil aue(79) akan membuat a memancarkan sinyal valueChanged(), yang akan diterima b di slot setValue()-nya, yaitu bue(79) dipanggil. b kemudian, pada gilirannya, akan memancarkan sinyal valueChanged() yang sama, tetapi karena tidak ada slot yang terhubung ke sinyal valueChanged() b, tidak ada yang terjadi (sinyal diabaikan).
Perhatikan bahwa fungsi setValue() menetapkan nilai dan memancarkan sinyal hanya jika v != val. Ini mencegah pengulangan tak terbatas dalam kasus koneksi siklik (misalnya jika bhanged() terhubung ke aue()).
Sinyal dipancarkan untuk setiap koneksi yang Anda buat, jadi jika Anda menduplikasi koneksi, dua sinyal akan dipancarkan. Anda selalu dapat memutuskan koneksi menggunakan QObject::disconnect().
Contoh ini mengilustrasikan bahwa objek dapat bekerja sama tanpa saling mengetahui, selama ada seseorang yang mengatur koneksi di antara mereka pada awalnya.
Preprocessor mengubah atau menghapus kata kunci signals, slots, dan emit sehingga kompiler disajikan dengan C++ standar.
Jalankan moc pada definisi kelas yang berisi sinyal atau slot. Ini menghasilkan file sumber C++ yang harus dikompilasi dan ditautkan dengan file objek lain untuk aplikasi. Jika Anda menggunakan qmake, aturan makefile untuk secara otomatis memanggil moc akan ditambahkan ke makefile Anda.
Sinyal
Sinyal dipancarkan oleh suatu objek ketika status internalnya telah berubah dengan cara yang mungkin menarik bagi klien atau pemilik objek. Hanya kelas yang mendefinisikan sinyal dan subkelasnya yang dapat memancarkan sinyal.
Sebuah kotak daftar, misalnya, memancarkan sinyal highlighted() dan activated(). Sebagian besar objek mungkin hanya tertarik pada activated(), tetapi beberapa mungkin ingin tahu tentang item mana dalam kotak daftar yang saat ini disorot. Jika sinyal menarik bagi dua objek yang berbeda, Anda cukup menghubungkan sinyal ke slot di kedua objek.
Ketika sinyal dipancarkan, slot yang terhubung dengannya dieksekusi segera, seperti panggilan fungsi normal. Mekanisme sinyal/slot sepenuhnya independen dari loop peristiwa GUI apa pun. Emit akan kembali ketika semua slot telah kembali.
Jika beberapa slot terhubung ke satu sinyal, slot akan dieksekusi satu per satu, dalam urutan sembarang, ketika sinyal dipancarkan.
Sinyal secara otomatis dihasilkan oleh moc dan tidak boleh diimplementasikan di file .cpp. Mereka tidak pernah dapat memiliki tipe pengembalian (yaitu gunakan void).
Catatan tentang argumen. Pengalaman kami menunjukkan bahwa sinyal dan slot lebih dapat digunakan kembali jika mereka tidak menggunakan tipe khusus. Jika QScrollBar::valueChanged() menggunakan tipe khusus seperti QRangeControl::Range hipotetis, itu hanya dapat dihubungkan ke slot yang dirancang khusus untuk QRangeControl. Sesuatu yang sederhana seperti program di Tutorial #1 bagian 5 tidak mungkin terjadi.
Slot
Sebuah slot dipanggil ketika sinyal yang terhubung dengannya dipancarkan. Slot adalah fungsi C++ normal dan dapat dipanggil secara normal; satu-satunya fitur khusus mereka adalah bahwa sinyal dapat terhubung ke mereka. Argumen slot tidak dapat memiliki nilai default, dan, seperti sinyal, jarang bijaksana untuk menggunakan tipe khusus Anda sendiri untuk argumen slot.
Karena slot adalah fungsi anggota normal dengan sedikit bumbu ekstra, mereka memiliki hak akses seperti fungsi anggota biasa. Hak akses slot menentukan siapa yang dapat terhubung ke sana:
Bagian public slots berisi slot yang dapat dihubungkan oleh siapa pun ke sinyal. Ini sangat berguna untuk pemrograman komponen: Anda membuat objek yang tidak tahu apa-apa tentang satu sama lain, menghubungkan sinyal dan slot mereka sehingga informasi melewati dengan benar, dan, seperti kereta api model, menyalakannya dan membiarkannya berjalan.
Bagian protected slots berisi slot yang dapat dihubungkan sinyal oleh kelas ini dan subkelasnya. Ini dimaksudkan untuk slot yang merupakan bagian dari implementasi kelas daripada antarmuka ke dunia luar.
Bagian private slots berisi slot yang hanya dapat dihubungkan sinyal oleh kelas itu sendiri. Ini dimaksudkan untuk kelas yang sangat terikat erat, di mana bahkan subkelas tidak dipercaya untuk mendapatkan koneksi yang benar.
Anda juga dapat mendefinisikan slot menjadi virtual, yang kami temukan cukup berguna dalam praktik.
Mekanisme sinyal dan slot efisien, tetapi tidak secepat callback "nyata". Sinyal dan slot sedikit lebih lambat karena fleksibilitas yang lebih besar yang mereka berikan, meskipun perbedaan untuk aplikasi nyata tidak signifikan. Secara umum, memancarkan sinyal yang terhubung ke beberapa slot, kira-kira sepuluh kali lebih lambat daripada memanggil penerima secara langsung, dengan panggilan fungsi non-virtual. Ini adalah overhead yang diperlukan untuk menemukan objek koneksi, untuk mengulangi semua koneksi dengan aman (yaitu memeriksa bahwa penerima berikutnya belum dihancurkan selama emisi) dan untuk marshaling parameter apa pun dengan cara generik. Sementara sepuluh panggilan fungsi non-virtual mungkin terdengar banyak, itu jauh lebih sedikit overhead daripada operasi 'new' atau 'delete' mana pun, misalnya. Segera setelah Anda melakukan operasi string, vektor, atau daftar yang di belakang layar memerlukan 'new' atau 'delete', overhead sinyal dan slot hanya bertanggung jawab untuk proporsi yang sangat kecil dari biaya panggilan fungsi lengkap. Hal yang sama berlaku setiap kali Anda melakukan panggilan sistem di slot; atau secara tidak langsung memanggil lebih dari sepuluh fungsi. Pada i586-500, Anda dapat memancarkan sekitar 2.000.000 sinyal per detik yang terhubung ke satu penerima, atau sekitar 1.200.000 per detik yang terhubung ke dua penerima. Kesederhanaan dan fleksibilitas mekanisme sinyal dan slot sangat sepadan dengan overhead, yang bahkan tidak akan disadari oleh pengguna Anda.
Informasi Meta Objek
Kompilator meta objek (moc) mengurai deklarasi kelas dalam file C++ dan menghasilkan kode C++ yang menginisialisasi meta objek. Meta objek berisi nama semua anggota sinyal dan slot, serta pointer ke fungsi-fungsi ini. (Untuk informasi lebih lanjut tentang Sistem Meta Objek Qt, lihat Mengapa Qt tidak menggunakan template untuk sinyal dan slot?)
Meta objek berisi informasi tambahan seperti nama kelas objek. Anda juga dapat memeriksa apakah suatu objek mewarisi kelas tertentu, misalnya:
if ( widget->inherits("QButton") ) { // ya, itu adalah tombol tekan, tombol radio dll. }Contoh Nyata
Berikut adalah contoh sederhana yang dikomentari (fragmen kode dari qlcdnumber.h).
#include "qframe.h" #include "qbitarray.h" class QLCDNumber : public QFrameQLCDNumber mewarisi QObject, yang memiliki sebagian besar pengetahuan sinyal/slot, melalui QFrame dan QWidget, dan menyertakan deklarasi yang relevan.
{ Q_OBJECTQ_OBJECT diperluas oleh preprocessor untuk mendeklarasikan beberapa fungsi anggota yang diimplementasikan oleh moc; jika Anda mendapatkan kesalahan kompiler seperti "virtual function QButton::className not defined", Anda mungkin lupa menjalankan moc atau menyertakan output moc dalam perintah tautan.
public: QLCDNumber( QWidget *parent=0, const char *name=0 ); QLCDNumber( uint numDigits, QWidget *parent=0, const char *name=0 );Ini tidak jelas relevan dengan moc, tetapi jika Anda mewarisi QWidget, Anda hampir pasti ingin memiliki argumen parent dan name di konstruktor Anda, dan meneruskannya ke konstruktor induk.
Beberapa destruktor dan fungsi anggota dihilangkan di sini; moc mengabaikan fungsi anggota.
signals: void overflow();QLCDNumber memancarkan sinyal ketika diminta untuk menampilkan nilai yang tidak mungkin.
Jika Anda tidak peduli tentang overflow, atau Anda tahu bahwa overflow tidak dapat terjadi, Anda dapat mengabaikan sinyal overflow(), yaitu jangan menghubungkannya ke slot mana pun.
Jika, sebaliknya, Anda ingin memanggil dua fungsi kesalahan yang berbeda ketika angka meluap, cukup hubungkan sinyal ke dua slot yang berbeda. Qt akan memanggil keduanya (dalam urutan sembarang).
public slots: void display( int num ); void display( double num ); void display( const char *str ); void setHexMode(); void setDecMode(); void setOctMode(); void setBinMode(); void smallDecimalPoint( bool );Slot adalah fungsi penerima, digunakan untuk mendapatkan informasi tentang perubahan status di widget lain. QLCDNumber menggunakannya, seperti yang ditunjukkan kode di atas, untuk menetapkan angka yang ditampilkan. Karena display() adalah bagian dari antarmuka kelas dengan program lainnya, slot bersifat publik.
Beberapa program contoh menghubungkan sinyal newValue() dari QScrollBar ke slot display(), sehingga nomor LCD terus menampilkan nilai scroll bar.
Perhatikan bahwa display() kelebihan beban (overloaded); Qt akan memilih versi yang sesuai saat Anda menghubungkan sinyal ke slot. Dengan callback, Anda harus menemukan lima nama berbeda dan melacak tipe sendiri.
Beberapa fungsi anggota yang tidak relevan telah dihilangkan dari contoh ini.