为了详细阐述模糊标题,我想大致了解元系统如何动态运行。
在静态上下文中使用插槽/属性访问器时,调用可能是内联的,毕竟如果有可能,为什么不呢?
但是动态场景和查询索引呢?元对象是如何实现的?偏移量是虚拟表中指针的一个吗?或者也许Qt创建自己的vtable而不是类虚拟方法使用的vtable?在这种情况下,虚拟属性方法是否在类“native”vtable以及为静态元对象创建的假设的额外复制中重复?实际呼叫在技术上是虚拟的吗?
我对错综复杂的细节不感兴趣,更像是整体概念。
答案 0 :(得分:0)
首先,当涉及便携式C ++时,没有“虚拟表”这样的东西。这是一个由编译器隐藏的实现细节。没有办法可移植地访问其内部(实现的数据结构),只有它的语义(它提供的功能)。
其次,你没有说出“呼叫”的意思。假设我们有
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
。
每个实现都会检查方法索引是否小于此类上的metamethods数。具有此信息的常量数据记录由moc生成。例如,QObject
有三个元方法 - 两个信号和一个插槽。如果索引太大于2,则会减少元方法的数量并返回到下一个派生类的qt_metacall
。
当QObject::qt_metacall
返回BaseObject::qt_metacall
时,索引已递减3,现在为1(4-3 = 1)。由于BaseObject
只有一个元方法(索引0),因此该索引减1并返回。
当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
的调用。