Як працюють сигнали та слоти в Qt, Блог розробників phpBB
Qt відмінно відомий своїм механізмом сигналів та слотів. Але як це працює? У цьому пості ми досліджуємо начинкиQObjectіQMetaObjectі розкриємо їхню роботу за кадром. Я даватиму приклади Qt5 коду, зрідка відредаговані для стислості та додавання форматування.
Сигнали та слоти
Для початку, пригадаємо, як виглядають сигнали та слоти, зазирнувши у формальний приклад. Заголовковий файл виглядає так:
Десь у .cpp файлі ми реалізуємоsetValue():
Після цього, можемо застосовувати об'єкт Counter таким чином:
Це справжній синаксис, який приблизно не змінювався з початку Qt в 1992 році. Але навіть якщо базове API не було змінено, реалізація змінювалася кілька разів. Під капотом додавалися нові можливості і відбувалися інші речі. Тут немає ніякої магії і я покажу, як це працює.
MOC або метаоб'єктний компілятор
Сигнали та слоти, а також система властивостей Qt базуються на ймовірностях самоаналізу об'єктів під час виконання програми. Самоаналіз позначає здатність перерахувати способи та властивості об'єкта та мати всю інформацію про них, зокрема, про типи їх доказів.QtScriptтаQMLнавряд чи були б допустимі без цього.
C не надає рідної підтримки самоаналізу, тому Qt поставляється з інструментом, який це забезпечує. Цей інструмент називаєтьсяMOC. Це кодогенератор (але не препроцесор, як вважають деякі люди).
Він парсить заголовні файли і генерує додатковий файл C, який компілюється з іншою частиною програми. Даний згенерований файл C містить всю інформацію, потрібну для самоаналізу. Qt зрідка піддається критиці з боку мовних пуристів, оскільки це додатковийгенератор коду. Я дозволю документації Qt відповісти на цю критику. Немає нічого поганого в кодогенераторі іMOCє чудовим помічником.
Чарівні макроси
Чи зможете ви помітити ключові слова, які не є ключовими словами C?signals,slots,Q_OBJECT,emit,SIGNAL,SLOT. Вони відомі як Qt-розтягування для C . Насправді це примітивні макроси, визначені в qobjectdefs.h.
Правда, сигнали та слоти є примітивними функціями: компілятор обробляє їх як і будь-які інші функції. Макроси ще служать певної мети:MOCбачить їх. Сигнали були у сегменти protected Qt4 і раніше. Але в Qt5 вони вже відкриті, для підтримки нового синтаксису.
Q_OBJECTвизначає зв'язок функцій та статичнийQMetaObject. Ці функції реалізовані у файлі, згенерованомуMOC.
emit- Порожній макрос. Він навіть не парситьсяMOC. Іншими словами,emitопціональний і нічого не означає (за винятком підказки для розробника).
Зараз перейдемо до коду, згенерованогоMOC.
QMetaObject
Тут бачимо реалізаціюCounter::metaObject()іCounter::staticMetaObject. Вони оголошені в макросіQ_OBJECT. QObject::d_ptr->metaObject застосовується тільки для динамічних метаоб'єктів (QMLоб'єкти), отже, у загальному випадку, віртуальна функціяmetaObject()легко повертає staticMetaObject класу. staticMetaObject побудований з даними тільки для читання.QMetaObjectвизначений у qobjectdefs.h у вигляді:
d неявно символізує, що це члени би мало бути приховані, але де вони приховані збереження POD і ймовірності статичної ініціалізації.
QMetaObjectініціалізується зпідтримкою метаоб'єкта батьківського класу superdata (QObject::staticMetaObject у разі). stringdata та data ініціалізуються деякими даними, які будуть розглянуті далі. static_metacall це покажчик на функцію, що ініціалізується Counter::qt_static_metacall.
Таблиці самоаналізу
По-перше, давайте подивимося основні даніQMetaObject.
Перші 13 int становлять заголовок. Він є двома колонками, перша колонка – це число, а друга – індекс масиву, де починається виклад. У цьому випадку ми маємо два способи, і виклад способів починається з індексу 14. Виклад способу складається з 5 int. 1-й – це ім'я, індекс у таблиці рядків (ми докладно розглянемо її пізніше). Друге ціле – число параметрів, за якими йде індекс, де ми можемо виявити їх виклад. Тепер ми будемо ігнорувати тег та прапори. Для будь-якої функціїMOCтакож зберігає тип будь-якого параметра, їх тип і індекс імені.
Таблиця рядків
В основному, це статичний масив QByteArray (створюваний макросомQT_MOC_LITERAL), який посилається на певний індекс у рядку нижче.
MOCтакож реалізує сигнали. Вони є функціями, які легко створюють масив покажчиків на аргументи і передають їхQMetaObject::activate. 1-й елемент масиву це значення, що повертається. У нашому прикладі це 0, тому що значення void, що повертається. 3-й аргумент, що передається функції для активізації, це індекс сигналу (0 в даному випадку).
Виклик слота
Також допустимо викликати слот за його індексом, застосовуючи qt_static_metacall:
Масив покажчиків на докази у такому форматі, як і сигналах. _a[0] не зачеплять, тому що всюди тут повертається void.
Примітка щодо індексів
Для будь-якогоQMetaObject, сигналів, слотам та іншим викликаним методам об'єкта, даються індекси, що починаються з 0. Вони впорядковані так, що на першому місці сигнали, потім слоти і потім вже інші методи. Ці індекси всередині називають відносними індексами. Вони не включають індекси батьків. Але загалом, ми не хочемо знати більше загальний індекс, який не відноситься до певного класу, але включає всі інші способи в ланцюжку успадкування. Отже, ми легко додаємо зсув до відносного індексу та отримуємо безумовний індекс. Цей індекс, застосовуваний у громадському API, повертається функціями типу QMetaObject::indexOf.
Механізм з'єднання використовує масив, індексований сигналів. Але всі слоти займають місце в цьому масиві і зазвичай слотів більше ніж сигналів. Так що з Qt 4.6 виникає новий внутрішній індекс для сигналів, який включає тільки індекси, що використовуються для сигналів. Якщо ви розробляєте Qt, вам необхідно знати тільки про безумовний індекс для способів. Але доки ви переглядаєте початковий кодQObject, ви повинні знати різницю між цими трьома індексами.
Як працює з'єднання
Перше, що робить Qt при з'єднанні, це шукає індекси сигналу та слота. Qt переглядатиме таблиці рядків метаоб'єкта у пошуках відповідних індексів. Після цього створюється і додається у внутрішні списки об'єкт QObjectPrivate::Connection.
Яка інформація потрібна для зберігання будь-якого з'єднання? Нам необхідний метод швидкого доступу до з'єднання для цього індексу сигналу. Так як можуть бути кілька слотів, приєднаних до одного й того самого сигналу, нам необхідно для будь-якого сигналу мати список приєднаних слотів. Будь-яке з'єднання має міститиоб'єкт-одержувач та індекс слота. Ми також хочемо, щоб з'єднання механічно видалялися, при видаленні одержувача, тому кожен об'єкт-одержувач повинен знати, хто з'єднаний з ним, щоб він міг видалити з'єднання.
Ось QObjectPrivate::Connection, визначений у qobject_p.h:
Кожен об'єкт має масив з'єднань: це масив, який об'єднує будь-якого сигналу списки QObjectPrivate::Connection. Кожен об'єкт також має зворотні списки з'єднань об'єктів, підключених до механічного видалення. Це двозв'язковий перелік.

Емісія сигналу
Коли ми викликаємо сигнал, ми бачили, що він викликає код, згенерованийMOC, той, що вже викликаєQMetaObject::activate. Ось реалізація (з примітками) цього способу qobject.cpp:
Від перекладача: це була перша частина і традиційним буде питання необхідності перекладу 2-ї частини.