Мой сигнал / слот не работает
Я неоднократно вижу людей, имеющих проблемы со слотами, которые не вызывают. Я хотел бы собрать некоторые из наиболее распространенных причин. Так что, возможно, я могу помочь людям и избежать множества лишних вопросов.
Каковы причины того, что соединения сигнал / слот не работают? Как можно избежать таких проблем?
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)
- Слишком много аргументов на правой стороне