QMetaObject :: indexOfMethod返回的索引究竟是什么?

时间:2014-01-26 16:21:41

标签: c++ qt properties metadata

为了详细阐述模糊标题,我想大致了解元系统如何动态运行。

在静态上下文中使用插槽/属性访问器时,调用可能是内联的,毕竟如果有可能,为什么不呢?

但是动态场景和查询索引呢?元对象是如何实现的?偏移量是虚拟表中指针的一个吗?或者也许Qt创建自己的vtable而不是类虚拟方法使用的vtable?在这种情况下,虚拟属性方法是否在类“native”vtable以及为静态元对象创建的假设的额外复制中重复?实际呼叫在技术上是虚拟的吗?

我对错综复杂的细节不感兴趣,更像是整体概念。

1 个答案:

答案 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

  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

    每个实现都会检查方法索引是否小于此类上的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来处理调用。

  3. 使用静态qt_static_metacall(虽然这是私有方法):

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

    qt_static_metacall是一个实现实际调用的静态方法。它只是打开基于0的本地索引,并调用该方法,传递它需要的任何参数。指向参数的指针在最后一个参数中传递 - 这里它只是一个nullptr,因为没有参数。这是一个简单无聊的C ++代码,没有任何魔力。

    我们使用索引4的方法确实是MyObject上的方法(而不是QObjectBaseObject)的知识。由于所有基类一起使用了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的调用。