Мое соединение с сигналом/слотом не работает

Я постоянно вижу людей, у которых проблемы со слотами, которые не вызываются. Я хотел бы собрать некоторые из наиболее распространенных причин. Поэтому, возможно, я смогу помочь людям и избежать множества лишних вопросов.

Каковы причины неработающих соединений с сигналом/слотом? Как можно избежать таких проблем?

Ответ 1

Существуют некоторые правила, которые облегчают жизнь с сигналами и слотами и охватывают наиболее распространенную причину дефектных соединений. Если я что-то забыл, пожалуйста, скажите мне.

1) Проверьте вывод консоли отладки:

Когда происходят ошибки выполнения, выходные данные отладки могут показать вам причину.

2) Используйте полную подпись сигнала и слота:

Вместо

connect(that, SIGNAL(mySignal), this, SLOT(mySlot));

записывать

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));

и проверьте правильность написания и заглавных букв.

3) Использовать существующие перегрузки:

Тщательно проверьте, используете ли вы требуемые перегрузки сигнала и слота и существуют ли фактически используемые вами перегрузки.

4) Ваш сигнал и слот должны быть совместимы:

Это особенно означает, что параметры должны быть одного типа (ссылки допускаются) и иметь одинаковый порядок.

Синтаксис времени компиляции также требует того же количества параметров. Старый синтаксис времени выполнения позволяет подключать сигналы к слотам с меньшим количеством параметров.

5) Всегда проверяйте возвращаемое значение метода connect (программисты никогда не должны игнорировать возвращаемые значения):

Вместо

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));

всегда используйте что-то вроде

bool success = connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));
Q_ASSERT(success);

Или, если вам нравится бросить исключение или реализовать полную обработку ошибок. Вы также можете использовать такой макрос:

#ifndef QT_NO_DEBUG
#define CHECK_TRUE(instruction) Q_ASSERT(instruction)
#else
#define CHECK_TRUE(instruction) (instruction)
#endif 

CHECK_TRUE(connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int))));

6) Вам нужен цикл событий для соединений в очереди:

Т.е. когда вы подключаете сигналы/слоты двух объектов, принадлежащих разным потокам (так называемые очереди), вам необходимо вызывать exec(); в слот темы!

Цикл событий также должен быть фактически обслужен. Всякий раз, когда поток слота застревает в каком-то цикле занятости, соединения в очереди НЕ выполняются!

7) Вам необходимо зарегистрировать пользовательские типы для подключений в очереди:

Поэтому при использовании пользовательских типов в подключенных к очереди подключениях вы должны зарегистрировать их для этой цели.

Сначала объявите тип, используя следующий макрос:

Q_DECLARE_METATYPE(MyType)

Затем используйте один из следующих вызовов:

qRegisterMetaType<MyTypedefType>("MyTypedefType"); // For typedef defined types
qRegisterMetaType<MyType>(); // For other types

8) Предпочитают новый синтаксис времени компиляции старому проверенному синтаксису во время выполнения:

Вместо

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));

использовать этот синтаксис

connect(that, &ThatObject::mySignal, this, &ThisObject::mySlot));

который проверяет сигнал и интервал во время компиляции и даже не требует, чтобы пункт назначения был фактическим интервалом.

Если ваш сигнал перегружен, используйте следующий синтаксис:

connect(that, static_cast<void (ThatObject::*)(int)> &ThatObject::mySignal), this, &ThisObject::mySlot); // <Qt5.7
connect(that, qOverload<int>::of(&ThatObject::mySignal), this, &ThisObject::mySlot); // >=Qt5.7 & C++11
connect(that, qOverload<int>(&ThatObject::mySignal), this, &ThisObject::mySlot); // >=Qt5.7 & C++14

Также не смешивайте постоянные/неконстантные сигналы/слоты для этого синтаксиса (обычно сигналы и слоты будут неконстантными).

9) Вашим классам нужен макрос Q_OBJECT:

В классах, где вы используете спецификации "сигналов" и "слотов", вам нужно добавить макрос Q_OBJECT следующим образом:

class SomeClass
{
   Q_OBJECT

signals:
   void MySignal(int x);
};

class SomeMoreClass
{
   Q_OBJECT

public slots:
   void MySlot(int x);
};

Этот макрос добавляет необходимую метаинформацию в класс.

10) Ваши объекты должны быть живы:

Как только объект отправителя или объект получателя уничтожен, Qt автоматически сбрасывает соединение.

Если сигнал не излучается: объект-отправитель все еще существует? Если слот не вызывается: объект-получатель все еще существует?

Чтобы проверить время жизни обоих объектов, используйте точку останова отладчика или какой-нибудь вывод qDebug() в конструкторах/деструкторах.

11) Всё равно не работает

Чтобы сделать очень быструю и грязную проверку вашего соединения, подайте сигнал самостоятельно, используя несколько фиктивных аргументов, и посмотрите, вызывается ли он:

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));
emit that->mySignal(0); // Ugly, don't forget to remove it immediately

Наконец, конечно, возможно, что сигнал просто не излучается. Если вы следовали приведенным выше правилам, возможно, что-то не так в логике вашей программы. Прочитайте документацию. Используйте отладчик. А если есть сейчас другой способ, спросите у stackoverflow.

Ответ 2

В моей практике я сталкивался со случаями некорректного переопределения eventFilter в объекте, получающем сигнал. Некоторые начинающие программисты забывают возвращать "false" в конце функции. И, таким образом, не позволяйте событию MetaCall проходить к принимающему объекту. В этом случае сигнал не обрабатывается на принимающем объекте.

Ответ 3

Короткий ответ

Вы (почти) больше не должны об этом беспокоиться. Всегда используйте QMetaMethod/Pointer для членства в прототипе connect, так как во время компиляции произойдет сбой, если сигнал и слот несовместимы.

connect(sourceObject, &SourceClass::signal, destObject, &DestClass::slot);

Этот прототип потерпит неудачу только во время выполнения, если sourceObject или destObject имеет значение null (что и следовало ожидать). Но несовместимость аргументов обнаружится во время компиляции

Только в редких случаях требуется более старый синтаксис на основе литералов SIGNAL/SLOT, так что это должно быть вашим последним средством.

Совместимость

Подписи совместимы, если выполняются следующие условия:

  • Вы подключаете сигнал к слоту или сигнал
  • Сигнал/слот назначения имеет то же число или меньше аргументов, что и сигнал источника
  • Аргументы исходного сигнала могут быть неявно преобразованы в соответствующий аргумент (соответствующий по порядку) в целевом сигнале/слоте, если он используется
Примеры
  • ОК - signalA(int, std::string) => signalC(int, std::string)
    • Обратите внимание, что мы подключаемся к сигналу
  • ОК - signalA(int, std::string) => slotB(int, std::string)
  • ОК - signalA(int, std::string) => slotB(int)
    • Строковый параметр игнорируется
  • ОК - signalA(int, std::string) => slotB()
    • Все параметры игнорируются
  • ОК - signalA(int, const char*) => slotB(int, QString)
    • Неявно преобразуется с помощью QString(const char*)
  • Сбой - signalA(int, std::string) => slotB(std::string)
    • int неявно преобразуется в std::string
  • Сбой - signalA(int, std::string) => slotB(std::string, int)
    • Неверный заказ
  • Сбой - signalA(int, std::string) => slotB(int, std::string, int)
    • Слишком много аргументов на правой стороне