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

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

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

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

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

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

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

Вам (почти) не нужно больше об этом беспокоиться. Всегда используйте QMetaMethod / Pointer to member prototype of 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)
    • Слишком много аргументов на правой стороне
Другие вопросы по тегам