QMetaType Знає Ваші Типи

Оригінал QMetaType knows your types з блогу на Woboq

QMetaType це шлях отримання Qt інформації про ваші типи під час виконання програми. Це дозволяє робити такі речі, як обгортання типів користувача в QVariant, копіювання аргументів при з'єднання типу Qt::QueuedConnection, та інші.

Якщо вас коли-небудь цікавило що роблять Q_DECLARE_META_TYPE чи qRegisterMetaType і коли їх використовувати (або не використовувати) - тоді читайте далі. Ця стаття розкаже все що вам потрібно знати про QMetaType: для чого він, як ним користуватись і як він працює.

Для чого Qt потрібна інформація про типи під час виконання програми?

Давайте почнемо з історії. QMetaType було представлено в Qt 4.0. Він був створений щоб отримати можливість реалізувати асинхронні сигнали і слоти (Qt::QueuedConnection). Для того щоб черга слотів(queued slots) працювала, необхідно скопіювати аргументи і зберегти їх в подію(event), яка опрацюється пізніше. Також необхідно видалити ці копії по завершенню виклику слота. (Примітка: Це не потрібно якщо використовувати Qt::DirectConnection: вказівники на аргументи використовуються безпосередньо зі стеку.)

Код, який відправляє(dispatching) сигнали QMetaObject::activate має масив вказівників на аргументи типу void*. (Для детальної інформації читайте Як працюють сигнали та слоти). Але, в той же час, все що Qt знає про типи аргументів - це їхні імена у вигляді стрічок, які були витягнуті за допомогою moc.

QMetaType дає можливість отримати копію або знищити об'єкт використовуючи стрічку(наприклад "QPoint"). В цьому випадку Qt буде використовувати void *QMetaType::create(int type, void *copy) і QMetaType::destroy(int type, void *data) для копіювання та знищення аргументів, де int type отримується за допомогою QMetaType::type(const char *typeName) використовуючи ім'я типу аргументу, який наданий moc. QMetaTypeтакож надає можливість для розробника зареєструвати будь який тип в базі мета-типів.

Ще одним способом використання QMetaType є QVariant. QVariant з Qt 3.x підтримував лише вбудовані типи Qt, тому що доданий(contained) довільний тип теж потрібно було б копіювати чи видаляти разом з QVariant, який його огортає. Але з допомогою QMetaType, QVariant отримав можливість містити будь який зареєстрований тип оскільки QVariant може сам копіювати і знищувати внутрішній екземпляр об'єкта.

Яку інформацію зберігає QMetaType?

Починаючи з Qt 4.0 багато чого змінилось. Ми отримали QtScript та QML за допомогою яких інтенсивніше почали використовувати інтеграцію динамічних типів. І ми оптимізували багато речей.

Ось інформація, яка зберігається в meta-type системі для кожного типу:

  • Зареєстроване ім'я типу. Існує індекс імені для швидкого пошуку ідентифікатора(ID) мета-типу. Починаючи з Qt 4.7, можливою стала навіть реєстрація того ж типу під іншим іменем (корисно для typedef).
  • Можливість створення (копіювання) та знищення (Copy) Constructor and Destructor (in-place or not).
  • Розмір щоб знати скільки місця потрібно виділити на стеку.
  • Прапорці які визначають таку ж інформацію як QTypeInfo чи тип перетворення.
  • Користувацькі функції для перетворення які встановлюються за допомогою QMetaType::registerConverter.
  • QMetaObject що містить QObject мета дані, що асоціюються з типом, якщо він існує.
  • ...

QTypeInfo

QTypeInfoє характерним класом, ортогональним до QMetaType, який дозволяє розробнику вручну визначати, використовуючи Q_DECLARE_TYPEINFO, чи тип можна переміщувати, використовуючи memmove, та чи потрібно запускати його конструктор та деструктор. Це переважно використовується для оптимізації в контейнерах типу QVector.

Для прикладу, implicit shared класи можуть бути переміщені з допомогою memmove, тоді як звичайна копія спочатку повинна збільшити лічильник посилань в конструкторі копіювання а потім зменшити його в деструкторі.

Для вирішення цієї проблеми в C++11 було представлено конструктори переміщення і характерні риси стандартних типів, але так як QTypeInfo було створено до C++11 і Qt досі працює з старими компіляторами, цей механізм не використовується.

Як це працює?

Так історично склалось, що існує велика різниця між вбудованими та власними створеними типами. Для вбудованих типів в QtCore, кожна мета функція це перемикач, який має спеціальний код для кожного типу. У Qt 5.0 це було змінено щоб якомога частіше використовувати шаблони. (Дивись QMetaTypeSwitcher). Нас цікавитиме, як це працює для власних створених типів.

Це просто QVector<QCustomTypeInfo> що містить в собі всю інформацію і вказівники функцій.

Макрос Q_DECLARE_METATYPE.

Цей макрос визначає шаблонний клас QMetaTypeId для конкретного типу. (Насправді, він визначає клас QMetaTypeId2 і в більшості випадків в коді використовується QMetaTypeId2. Можливо через це Qt може додати більше вбудованих типів не ламаючи код, який використовував Q_DECLARE_METATYPE до цього.)

QMetaTypeId використовується щоб визначити ID мета-типу на етапі компіляції. QMetaTypeId::qt_metatype_id це функція яка викликається qMetaType<T>(). При першому викликові функції, будуть викликані деякі внутрішні функції QMetaType щоб зареєструвати та виділити пам'ять під ID мета-типу, використовуючи ім'я визначене макросом. Це ID буде зберігатись в статичній змінній.

Окрім імені, вся інша інформація автоматично визначиться компілятором за допомогою шаблонів.

qRegisterMetaType

Тип, зареєстрований з допомогою Q_DECLARE_METATYPE, буде насправді зареєстровано (і йому буде присвоєно ID) при першому використанні qMetaTypeId(). Наприклад, це випадок коли тип загорнути в QVariant. Він не буде зареєстрованим при з'єднанні сигналів і слотів, бо в цьому випадку ви повинні спровокувати реєстрацію викликавши qRegisterMetaType.

Автоматична реєстрація.

Розробники часто забувають реєструвати свої мета-типи поки не отримують помилку компіляції або помилку на етапі виконання яка каже їм зробити це. Але чи не було б прекрасно, якби це не було необхідним? Єдина причина, чому Q_DECLARE_METATYPE є необхідним це отримання імені. Але є випадки коли ми можемо дізнатись ім'я на етапі виконання без цього макросу. Для прикладу QList<T>. Якщо T вже зареєстровано, ми можемо, запитавши систему мета-типів, збудувати ім'я використовуючи "QList<" + QMetaType::name(qMetaTypeId<T>()) + ">".

Ми робимо це для групи шаблонних класів, наприклад: QList, QVector, QSharedPointer, QPointer, QMap, QHash, .... Завдяки інформації, яку надає moc, ми також можемо визначити ім'я вказівника на наслідуванні від QObjectкласи: T::staticMetaObject.className() + "*". І починаючи з Qt 5.5, ми можемо автоматично оголошувати Q_GADGET і Q_ENUM.

Це все що пов'язано з Q_DECLARE_METATYPE, але якщо вам потрібно використати ваш в Q_PROPERTY або як параметр в сигнально-слотовому з'єднанні типу Qt::QueuedConnection вам все одно доведеться викликати qRegisterMetaType. Проте, починаючи з Qt 5.x, код, який генерує moc, буде викликати qRegisterMetaType за вас, якщо moc може визначити що цей тип може бути зареєстровано як мета-тип.

Дослідження

До Qt 5.0, я намагався дослідити чи є можливість позбутися Q_DECLARE_METATYPE у випадках, коли нам не потрібне ім'я. Це працювало якось так:

template<typename T> QMetaTypeId {
    static int qt_metatype_id() {
        static int typeId = QMetaType::registerMetaType(/*...*/);
        return typeId;
    }
};

Відповідно до стандарту C++, для кожного типу повинен бути лише один екземпляр змінної QMetaTypeId::qt_metatype_id()::typeId. Але на практиці деякі компілятори чи лінкери не дотримуються цього правила. На Windows, для прикладу, буде один екземпляр на бібліотеку, навіть якщо використовувати відповідний експорт макрос. Відповідно, нам завжди буде потрібен ідентифікатор імені, якого ми не маємо. (І ми не хочемо покладатись на RTII). Тому ми реєструємо тільки ті типи, для яких ми можемо знати ім'я в Qt 5.0.

Стаття опублікована Олівером Ґоффартом (Olivier Goffart) 22 квітня 2015 року.

results matching ""

    No results matching ""