Підключення перевантажених сигналів та слотів у Qt 5


133

У мене виникають проблеми з утисканням нового синтаксису сигнал / слот (використовуючи функцію вказівника на член) у Qt 5, як описано в Syntax New Signat Slot . Я спробував змінити це:

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

до цього:

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

але я отримую помилку, коли намагаюся її скласти:

помилка: немає функції узгодження для дзвінка на QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Я намагався з clang та gcc на Linux, як з -std=c++11.

Що я роблю неправильно, і як це можна виправити?


Якщо ваш синтаксис правильний, то єдиним поясненням може бути те, що ви не посилаєтесь на бібліотеки Qt5, а натомість, наприклад, Qt4. Це легко підтвердити за допомогою QtCreator на сторінці "Проекти".
Метт Філліпс

Я включив деякі підкласи QObject (QSpinBox тощо), так що повинен був включати QObject. Я намагався додати, що включати, а також, хоча він все ще не буде компілюватися.
dtruby

Крім того, я напевно посилаюся на Qt 5, я використовую Qt Creator, і два комплекти, які я тестую, обидва мають Qt 5.0.1, вказаний як їх версія Qt.
dtruby

Відповіді:


244

Проблема тут полягає в тому, що є два сигнали з таким ім'ям: QSpinBox::valueChanged(int)і QSpinBox::valueChanged(QString). З Qt 5.7 передбачені допоміжні функції для вибору потрібного перевантаження, щоб ви могли писати

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

Для Qt 5.6 і новіших версій вам потрібно сказати Qt, який ви бажаєте вибрати, переклавши його на потрібний тип:

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

Я знаю, це некрасиво . Але обходу цього немає. Сьогоднішній урок такий: не перевантажуйте свої сигнали та слоти!


Додаток : те, що насправді дратує в ролях, це те

  1. один повторює назву класу двічі
  2. треба вказати повернене значення, навіть якщо воно зазвичай void(для сигналів).

Тож я іноді опиняюся за допомогою цього фрагмента C ++ 11:

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

Використання:

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

Я особисто вважаю це не дуже корисним. Я очікую, що ця проблема сама собою відірветься, коли Creator (або ваш IDE) автоматично вставить правильний склад при автоматичному завершенні операції з прийняття PMF. Але тим часом ...

Примітка: синтаксис з'єднання на основі PMF не вимагає C ++ 11 !


Додаток 2 : у Qt 5.7 для доповнення цього було додано допоміжні функції, зразки мого вирішення вище. Основний помічник - це qOverload(ви також отримали qConstOverloadі qNonConstOverload).

Приклад використання (з документів):

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)

Додаток 3 : якщо ви подивитесь на документацію будь-якого перевантаженого сигналу, тепер вирішення проблеми з перевантаженням чітко зазначено в самих документах. Наприклад, https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1 каже

Примітка: Значення сигналу Змінено перевантажено в цьому класі. Для підключення до цього сигналу за допомогою синтаксису вказівника функції Qt надає зручний помічник для отримання вказівника функції, як показано в цьому прикладі:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });

1
Ага так, це має багато сенсу. Я думаю, що для таких випадків, коли сигнали / слоти перевантажені, я просто дотримуюся старого синтаксису :-). Дякую!
dtruby

17
Мене так схвилював новий синтаксис ... тепер холодний сплеск замороженого розчарування.
RushPL

12
Для тих, хто цікавиться (як я): "pmf" означає "покажчик на функцію члена".
Vicky Chijwani

14
Я особисто віддаю перевагу static_castнеподобству над старим синтаксисом, просто тому, що новий синтаксис дозволяє перевірити час компіляції на наявність сигналу / слота, де старий синтаксис не міг би виконуватись під час виконання.
Vicky Chijwani

2
На жаль, часто не перевантажувати сигнал - це не варіант - Qt часто перевантажує власні сигнали. (наприклад QSerialPort)
PythonNut

14

Повідомлення про помилку:

помилка: немає функції узгодження для дзвінка на QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

Важливою частиною цього є згадка про " невирішений перевантажений тип функції ". Компілятор не знає, ви маєте на увазі QSpinBox::valueChanged(int)чи QSpinBox::valueChanged(QString).

Існує кілька способів вирішити перевантаження:

  • Надайте відповідний параметр шаблону для connect()

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

    Це змушує connect()вирішити &QSpinBox::valueChangedперевантаження, яке приймає int.

    Якщо у вас є невирішені перевантаження для аргументу слота, вам потрібно буде надати другий аргумент шаблону connect(). На жаль, не існує синтаксису, який би просив зробити висновок першого, тому вам потрібно буде подати обидва. Ось тоді другий підхід може допомогти:

  • Використовуйте тимчасову змінну правильного типу

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

    Завдання на signalвибір вибере бажану перевантаження, і тепер її можна успішно замінити в шаблон. Це однаково добре працює з аргументом "слот", і я вважаю його менш громіздким у такому випадку.

  • Використовуйте конверсію

    Тут ми можемо уникати static_cast, оскільки це просто примус, а не зняття захистів мови. Я використовую щось на кшталт:

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

    Це дозволяє нам писати

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

8

Насправді ви можете просто обернути свій слот лямбда, і це:

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

буде виглядати краще. : \


0

Вирішення вище працює, але я вирішив це дещо по-іншому, використовуючи макрос, Тож про всяк випадок тут це:

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

Додайте це у свій код.

Тоді, ваш приклад:

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

Стає:

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

2
Рішення "вище", що? Не вважайте, що відповіді подаються всім у тому порядку, який ви їх бачите!
Toby Speight

1
Як ви використовуєте це для перевантажень, які беруть більше одного аргументу? Не викликає кома проблем? Я думаю, що вам дійсно потрібно передавати паролі, тобто #define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)- використовувані як CONNECTCAST(QSpinBox, valueChanged, (double))у цьому випадку.
Toby Speight

це приємний корисний макрос, коли дужки використовуються для декількох аргументів, як у коментарі Тобі
ejectamenta
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.