QMetaObject::indexOfMethod возвращает индекс чего именно?

Чтобы уточнить двусмысленное название, я хотел бы примерно знать, как метасистема работает динамически.

При работе со слотами/аксессорами свойств в статическом контексте вызовы, вероятно, встроены, в конце концов, если это возможно, почему бы и нет ??

Но как насчет динамических сценариев и запросов индексов? Как реализуется метаобъект? Является ли смещение одним из указателей в виртуальной таблице? Или, может быть, Qt создает свою собственную виртуальную таблицу вместо той, которая используется виртуальными методами класса? В этом случае дублируются ли методы виртуального свойства как в «собственной» vtable класса, так и в гипотетическом дополнительном, созданном для статического метаобъекта? Являются ли фактические звонки технически виртуальными?

Меня не интересуют замысловатые детали, больше нравится общая концепция.


person dtech    schedule 26.01.2014    source источник


Ответы (1)


Во-первых, когда речь идет о переносимом С++, не существует такой вещи, как "виртуальная таблица". Это деталь реализации, скрытая компилятором. Нет способа переносимого доступа к его внутренним компонентам (структуре данных реализации), только к его семантике (предлагаемым функциям).

Во-вторых, вы не говорите, что подразумеваете под «звонком». Предположим, у нас есть

class BaseObject : public QObject {
  Q_OBJECT
public:
  Q_SIGNAL void mySignal();
}

class MyObject : public BaseObject {
  Q_OBJECT
public:
  Q_SLOT void mySlot();
};
MyObject myObject;

Существует несколько способов вызова mySlot.

  1. Вызов его напрямую:

    myObject.mySlot("yay!")
    

    Это ничем не отличается от вызова любого другого метода — то, что это слот, не делает его особенным с точки зрения C++. Если это виртуальный метод, то это вызов виртуального метода со всеми накладными расходами, которые это влечет за собой на данной платформе.

  2. Используя виртуальный метод qt_metacall с индексом метода:

    myObject.qt_metacall(QMetaObject::InvokeMetaMethod, 4, nullptr);
    

    Реализация qt_metacall создается moc. qt_metacall это место, где определяются индексы метода. Внутри qt_metacall вызывает себя рекурсивно вплоть до QObject::qt_metacall.

    Каждая реализация проверяет, меньше ли индекс метода, чем количество метаметодов в этом классе. Постоянная запись данных с этой информацией генерируется moc. Например, QObject имеет три метаметода — два сигнала и один слот. Если индекс больше 2, он уменьшается на количество метаметодов и возвращается к qt_metacall следующего производного класса.

    Когда QObject::qt_metacall возвращается в BaseObject::qt_metacall, индекс уменьшается на 3 и теперь равен единице (4-3 = 1). Поскольку BaseObject имеет только один метаметод (индекс 0), этот индекс уменьшается на единицу и возвращается.

    Когда BaseObject::qt_metacall возвращается в MyObject::qt_metacall, индекс уменьшается на (3+1=4) и теперь равен нулю (0). Это локальный индекс одинокого mySlot, и вызов обрабатывается путем передачи индекса MyObject::qt_static_metacall.

  3. Используя статический qt_static_metacall (хотя это частный метод):

    MyObject::qt_static_metacall(&myObject, QMetaObject::InvokeMethod, 0, nullptr);
    

    qt_static_metacall — это статический метод, реализующий фактический вызов. Он просто включает локальный индекс, отсчитываемый от 0, и вызывает метод, передавая ему все необходимые аргументы. Указатели на аргументы передаются в последнем аргументе — здесь это просто nullptr, так как аргументов нет. Это обычный скучный код C++, никакой магии.

    Мы используем знание того, что метод с индексом 4 действительно является методом на MyObject (а не, скажем, на QObject или BaseObject). Так как все базовые классы вместе используют 4 индекса вверх, мы уменьшаем индекс метода на такую ​​же величину — до нуля (4-4 = 0).

    Итак, если вы знаете, какой конкретный класс реализует индекс метода, вы можете вызвать статический метод напрямую, без использования рекурсивного виртуального qt_metacall. Этот поиск выполняется QObject::connect при установке соединения. Место назначения соединения сохраняется как локальный индекс метода и указатель на метод qt_static_metacall класса, у которого есть данный метод. Это снижает стоимость рекурсии qt_metacall, когда слот вызывается подключенным сигналом.

  4. Используя QMetaObject::invokeMethod:

    QMetaObject::invokeMethod(&myObject, "mySlot");
    

    Он выполняет все те же операции поиска, что и QObject::connect, но вместо установки соединения сразу же выполняет вызов. Опять же, это закончится на MyObject::qt_static_metacall.

  5. Используя QMetaMethod::invoke:

    QMetaMethod method = myObject.metaObject()->method(
                           myObject.metaObject->indexOfSlot("mySlot()"));
    method.invoke(myObject);
    

    QMetaObject кэширует искомый указатель на MyObject::qt_static_metacall, а также локальный индекс метода 0. Таким образом, вызов invoke имеет меньше накладных расходов, чем вызов из QMetaObject.

person Kuba hasn't forgotten Monica    schedule 27.01.2014