Чтобы уточнить двусмысленное название, я хотел бы примерно знать, как метасистема работает динамически.
При работе со слотами/аксессорами свойств в статическом контексте вызовы, вероятно, встроены, в конце концов, если это возможно, почему бы и нет ??
Но как насчет динамических сценариев и запросов индексов? Как реализуется метаобъект? Является ли смещение одним из указателей в виртуальной таблице? Или, может быть, Qt создает свою собственную виртуальную таблицу вместо той, которая используется виртуальными методами класса? В этом случае дублируются ли методы виртуального свойства как в "собственной" vtable класса, так и в гипотетическом дополнительном, созданном для статического метаобъекта? Являются ли фактические звонки технически виртуальными?
Меня не интересуют замысловатые детали, больше нравится общая концепция.
Ответ 1 · рейтинг 1 · Kuba hasn't forgotten Monica
Во-первых, когда речь идет о переносимом С++, не существует такой вещи, как "виртуальная таблица". Это деталь реализации, скрытая компилятором. Нет способа переносимого доступа к его внутренним компонентам (структуре данных реализации), только к его семантике (предлагаемым функциям).
Во-вторых, вы не говорите, что подразумеваете под "звонком". Предположим, у нас есть
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.
Вызов его напрямую:
myObject.mySlot("yay!")
Это ничем не отличается от вызова любого другого метода - то, что это слот, не делает его особенным с точки зрения C++. Если это виртуальный метод, то это вызов виртуального метода со всеми накладными расходами, которые это влечет за собой на данной платформе.
Используя виртуальный метод 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.
Используя статический 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, когда слот вызывается подключенным сигналом.
Используя QMetaObject::invokeMethod:
QMetaObject::invokeMethod(&myObject, "mySlot");
Он выполняет все те же операции поиска, что и QObject::connect, но вместо установки соединения сразу же выполняет вызов. Опять же, это закончится на MyObject::qt_static_metacall.
Используя QMetaMethod::invoke:
QMetaMethod method = myObject.metaObject()->method(
myObject.metaObject->indexOfSlot("mySlot()"));
method.invoke(myObject);
QMetaObject кэширует искомый указатель на MyObject::qt_static_metacall, а также локальный индекс метода 0. Таким образом, вызов invoke имеет меньше накладных расходов, чем вызов из QMetaObject.