Как работят сигналите и слотовете в 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 ключови думи?сигнали,слотове,Q_OBJECT,излъчване,СИГНАЛ,СЛОТ. Те са известни като Qt разширение за C. Всъщност това са примитивни макроси, които са дефинирани в qobjectdefs.h.
Вярно е, сигналите и слотовете са примитивни функции: компилаторът ги третира като всяка друга функция. Макросите все още служат за цел:MOCги вижда. Сигналите бяха в защитени сегменти в 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се инициализира споддръжка за мета-обект на родителския клас на суперданни (в този случай QObject::staticMetaObject). stringdata и data се инициализират с някои данни, които ще бъдат обсъдени по-нататък. static_metacall е указател на функция, инициализиран от Counter::qt_static_metacall.
Маси за интроспекция
Първо, нека да разгледаме основните данни наQMetaObject.
Първите 13 int съставляват заглавката. Предоставя две колони, първата колона е числото, а втората е индексът на масива, където започва историята. В настоящия случай имаме два начина и операторът за начин започва от индекс 14. Инструкцията за начин се състои от 5 int. Първото е името, индексът в таблицата с низове (ще го разгледаме подробно по-късно). Второто цяло число е броят на параметрите, последван от индекс, където можем да намерим тяхното представяне. Сега ще игнорираме етикета и флаговете. За всяка функцияMOCсъщо така запазва върнатия тип на всеки параметър, техния тип и индекса на името.
Таблица с низове
По принцип това е статичен QByteArray (създаден от макросаQT_MOC_LITERAL), който препраща към конкретен индекс на реда по-долу.
MOCсъщо прилага сигнали. Те са функции, които лесно създават масив от указатели на аргументи и ги предават наQMetaObject::activate. Първият елемент на масива е върнатата стойност. В нашия пример това е 0, защото върнатата стойност е празна. Третият аргумент, предаден на функцията за активиране, е индексът на сигнала (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 на всеки сигнал. Всеки обект също има обърнати списъци с връзки на обекти, свързани за механично премахване. Това е двойно свързан списък.
Свързаните списъци се използват за увеличаване на вероятността от бързо добавяне и премахване на обекти. Те се реализират с указатели за следващ/предишен възел вътре в QObjectPrivate::Connection. Имайте предвид, че предишният указател от senderList е указател към указател. Това е така, защото ние наистина не сочим към предишния възел, а по-скоро към следващия, в предишния възел. Този указател се използва само когато връзката е унищожена. Това позволява да няма специален случай за първия елемент.Излъчване на сигнал
Когато извикваме сигнал, видяхме, че той извиква кода, генериран отMOC, този, който извикваQMetaObject::activate. Ето реализацията (с бележки) на този метод в qobject.cpp:
От преводача: това беше първата част и ще бъде традиционно да питаме за необходимостта от превод на втората част.