Menghubungkan sinyal dan slot berlebih di Qt 5

133

Saya mengalami masalah untuk memahami sintaks sinyal / slot baru (menggunakan fungsi pointer ke anggota) di Qt 5, seperti yang dijelaskan dalam Sintaks Slot Sinyal Baru . Saya mencoba mengubah ini:

QObject::connect(spinBox, SIGNAL(valueChanged(int)),
                 slider, SLOT(setValue(int));

untuk ini:

QObject::connect(spinBox, &QSpinBox::valueChanged,
                 slider, &QSlider::setValue);

tapi saya mendapatkan kesalahan saat mencoba mengompilasinya:

kesalahan: tidak ada fungsi yang cocok untuk panggilan ke QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Saya sudah mencoba dengan dentang dan gcc di Linux, keduanya dengan -std=c++11.

Apa yang saya lakukan salah, dan bagaimana saya bisa memperbaikinya?

dtruby
sumber
Jika sintaks Anda benar, maka satu-satunya penjelasan adalah bahwa Anda tidak menautkan ke pustaka Qt5, tetapi misalnya Qt4 sebagai gantinya. Ini mudah diverifikasi dengan QtCreator di halaman 'Proyek'.
Matt Phillips
Saya menyertakan beberapa subclass dari QObject (QSpinBox dll.) Sehingga seharusnya sudah termasuk QObject. Saya memang mencoba menambahkan itu termasuk juga dan masih tidak dapat dikompilasi.
dtruby
Juga, saya pasti terhubung dengan Qt 5, saya menggunakan Qt Creator dan dua kit yang saya uji dengan keduanya memiliki Qt 5.0.1 terdaftar sebagai versi Qt mereka.
dtruby

Jawaban:

244

Masalahnya di sini adalah bahwa ada dua sinyal dengan nama itu: QSpinBox::valueChanged(int)dan QSpinBox::valueChanged(QString). Dari Qt 5.7, ada fungsi pembantu yang disediakan untuk memilih kelebihan yang diinginkan, sehingga Anda dapat menulis

connect(spinbox, qOverload<int>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

Untuk Qt 5.6 dan sebelumnya, Anda perlu memberi tahu Qt mana yang ingin Anda pilih, dengan melemparkannya ke jenis yang tepat:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

Saya tahu, itu jelek . Tapi tidak ada jalan lain untuk ini. Pelajaran hari ini adalah: jangan membebani sinyal dan slot Anda!


Tambahan : apa yang benar-benar mengganggu tentang para pemain adalah itu

  1. satu mengulangi nama kelas dua kali
  2. kita harus menentukan nilai kembali bahkan jika biasanya void(untuk sinyal).

Jadi saya kadang menemukan diri saya menggunakan cuplikan C ++ 11 ini:

template<typename... Args> struct SELECT { 
    template<typename C, typename R> 
    static constexpr auto OVERLOAD_OF( R (C::*pmf)(Args...) ) -> decltype(pmf) { 
        return pmf;
    } 
};

Pemakaian:

connect(spinbox, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged), ...)

Saya pribadi merasa itu tidak terlalu berguna. Saya berharap masalah ini hilang dengan sendirinya ketika Creator (atau IDE Anda) akan secara otomatis memasukkan gips yang tepat ketika secara otomatis menyelesaikan operasi pengambilan PMF. Namun sementara itu ...

Catatan: sintaks koneksi berbasis PMF tidak memerlukan C ++ 11 !


Tambahan 2 : dalam Qt 5.7 fungsi pembantu ditambahkan untuk mengurangi ini, dimodelkan setelah solusi saya di atas. Pembantu utama adalah qOverload(Anda juga punya qConstOverloaddanqNonConstOverload ).

Contoh penggunaan (dari dokumen):

struct Foo {
    void overloadedFunction();
    void overloadedFunction(int, QString);
};

// requires C++14
qOverload<>(&Foo:overloadedFunction)
qOverload<int, QString>(&Foo:overloadedFunction)

// same, with C++11
QOverload<>::of(&Foo:overloadedFunction)
QOverload<int, QString>::of(&Foo:overloadedFunction)

Tambahan 3 : jika Anda melihat dokumentasi dari setiap sinyal kelebihan beban, sekarang solusi untuk masalah kelebihan muatan tersebut dinyatakan dengan jelas dalam dokumen itu sendiri. Misalnya, https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1 kata

Catatan: Nilai sinyal Diubah kelebihan muatan di kelas ini. Untuk menyambung ke sinyal ini dengan menggunakan sintaks penunjuk fungsi, Qt menyediakan penolong yang nyaman untuk mendapatkan penunjuk fungsi seperti yang ditunjukkan dalam contoh ini:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });
peppe
sumber
1
Ah ya, itu masuk akal. Saya kira untuk kasus-kasus seperti ini di mana sinyal / slot kelebihan beban saya hanya akan tetap dengan sintaks lama :-). Terima kasih!
dtruby
17
Saya sangat bersemangat tentang sintaks baru ... sekarang percikan dingin kekecewaan.
RushPL
12
Bagi mereka yang bertanya-tanya (seperti saya): "PMF" singkatan dari "pointer ke fungsi anggota".
Vicky Chijwani
14
Saya pribadi lebih suka static_castkeburukan daripada sintaks lama, hanya karena sintaks baru memungkinkan pemeriksaan waktu kompilasi untuk keberadaan sinyal / slot di mana sintaks lama akan gagal saat runtime.
Vicky Chijwani
2
Sayangnya, tidak membebani sinyal sering kali bukan pilihan — Qt sering membebani sinyal mereka sendiri. (mis. QSerialPort)
PythonNut
14

Pesan kesalahan adalah:

kesalahan: tidak ada fungsi yang cocok untuk panggilan ke QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Bagian penting dari ini adalah penyebutan " tipe fungsi kelebihan beban yang tidak terselesaikan ". Kompiler tidak tahu apakah maksud Anda QSpinBox::valueChanged(int)atau QSpinBox::valueChanged(QString).

Ada beberapa cara untuk mengatasi kelebihan:

  • Berikan parameter templat yang cocok untuk connect()

    QObject::connect<void(QSpinBox::*)(int)>(spinBox, &QSpinBox::valueChanged,
                                             slider,  &QSlider::setValue);

    Ini memaksa connect()untuk menyelesaikan &QSpinBox::valueChangedkelebihan yang dibutuhkan int.

    Jika Anda memiliki kelebihan berlebih yang belum terselesaikan untuk argumen slot, maka Anda harus menyediakan argumen templat kedua connect(). Sayangnya, tidak ada sintaks untuk meminta yang pertama disimpulkan, jadi Anda harus menyediakan keduanya. Saat itulah pendekatan kedua dapat membantu:

  • Gunakan variabel sementara dari jenis yang benar

    void(QSpinBox::*signal)(int) = &QSpinBox::valueChanged;
    QObject::connect(spinBox, signal,
                     slider,  &QSlider::setValue);

    Tugas untuk signal akan memilih kelebihan yang diinginkan, dan sekarang dapat diganti dengan sukses ke dalam templat. Ini berfungsi sama baiknya dengan argumen 'slot', dan saya merasa kurang rumit dalam kasus itu.

  • Gunakan konversi

    Kita dapat menghindari di static_castsini, karena itu hanya paksaan alih-alih menghapus perlindungan bahasa. Saya menggunakan sesuatu seperti:

    // Also useful for making the second and
    // third arguments of ?: operator agree.
    template<typename T, typename U> T&& coerce(U&& u) { return u; }

    Ini memungkinkan kita untuk menulis

    QObject::connect(spinBox, coerce<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
                     slider, &QSlider::setValue);
Toby Speight
sumber
8

Sebenarnya, Anda bisa membungkus slot Anda dengan lambda dan ini:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
    slider, &QSlider::setValue);

akan terlihat lebih baik. : \

Newlifer
sumber
0

Solusi di atas berfungsi, tetapi saya memecahkan ini dengan cara yang sedikit berbeda, menggunakan makro, Jadi untuk berjaga-jaga di sini adalah:

#define CONNECTCAST(OBJECT,TYPE,FUNC) static_cast<void(OBJECT::*)(TYPE)>(&OBJECT::FUNC)

Tambahkan ini dalam kode Anda.

Lalu, contoh Anda:

QObject::connect(spinBox, &QSpinBox::valueChanged,
             slider, &QSlider::setValue);

Menjadi:

QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged),
             slider, &QSlider::setValue);
Basile Perrenoud
sumber
2
Solusi "di atas" apa? Jangan berasumsi bahwa jawaban diberikan kepada semua orang sesuai urutan yang Anda lihat sekarang!
Toby Speight
1
Bagaimana Anda menggunakan ini untuk overload yang membutuhkan lebih dari satu argumen? Bukankah koma menyebabkan masalah? Saya pikir Anda benar-benar harus lulus parens, yaitu #define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)- digunakan seperti CONNECTCAST(QSpinBox, valueChanged, (double))dalam kasus ini.
Toby Speight
ini adalah makro berguna yang bagus ketika tanda kurung digunakan untuk banyak argumen, seperti dalam komentar Toby
ejectamenta