`
gaofen100
  • 浏览: 1243192 次
文章分类
社区版块
存档分类
最新评论

Qt 之 qobject_cast 分析及QTBUG 20616

 
阅读更多

适用范围

qobject_cast 用于两种情况:

  • QObject 及其派生类,且定义时使用了Q_OBJECT
  • Q_DECLARE_INTERFACE 声明的接口类(不需要是QObject的派生类)

问题:如果既是QObject派生类,又用Q_DECLARE_INTERFACE声明为接口会怎么样?QTBUG 20616报告的就是这样一个问题。

QObject的派生类

首先看看qobject_cast的manual:

T qobject_cast ( QObject * object )

Returns the given object cast to type T if the object is of type T (or of a subclass); otherwise returns 0. If object is 0 then it will also return 0.

The class T must inherit (directly or indirectly) QObject and be declared with the Q_OBJECT macro.
...

恩,要求很明确,必须继承自QObject,且包含有Q_OBJECT宏(即需要moc生成metaobject信息)。

源码

看看源码(来自qobject.h,注意,随Qt的版本不同,你看到的源码可能会有很大的差异):

template <class T>
inline T qobject_cast(QObject *object)
{
    return static_cast<T>(reinterpret_cast<T>(object)->staticMetaObject.cast(object));
}

注:

  • 还有一个对应的template<classT>inlineTqobject_cast(constQObject*),由于代码基本完全一样,本文中直接忽略。

  • 先前(比如Qt4.6.3)曾用过returnstatic_cast<T>(((T)0)->staticMetaObject.cast(object));这种写法。

  • 不管怎么样,转型的操作是通过 static_cast 来实现的,而 QMetaObject::cast 只是不过借助 metaobject 信息来确定对象object是否继承自 T

/*!
    \internal

    Returns \a obj if object \a obj inherits from this
    meta-object; otherwise returns 0.
*/
QObject *QMetaObject::cast(QObject *obj) const
{
    if (obj) {
        const QMetaObject *m = obj->metaObject();
        do {
            if (m == this)
                return obj;
        } while ((m = m->d.superdata));
    }
    return 0;
}

inherits

如何我们看moc生成的元对象文件,比如,对于一个继承子QWidget的类HLed:moc_hled.cpp

void *HLed::qt_metacast(const char *_clname)
{
    if (!_clname) return 0;
    if (!strcmp(_clname, qt_meta_stringdata_HLed))
        return static_cast<void*>(const_cast< HLed*>(this));
    return QWidget::qt_metacast(_clname);
}

这个东西看起来和转型动作关系很大啊,可是前面的qobject_cast却没有用到它,那么它用在哪儿呢?

似乎只用在inherits中(fixme ?)

class QObject
{
...
    inline bool inherits(const char *classname) const
        { return const_cast<QObject *>(this)->qt_metacast(classname) != 0; }
...

注意1:qt_metacast 中比较的是类名qt_meta_stringdata_HLed这样字符串,后者'\0'前面部分其实就是类名。

static const char qt_meta_stringdata_HLed[] = {
    "HLed\0\0toggle()\0on()\0off()\0blink()\0"
    "blinkToggle()\0"
};

注意2:后面我们会再次遇到qt_metacast,注意与此处的区别。

Q_DECLARE_INTERFACE声明的接口

还是看qobject_cast的manual

T qobject_cast ( QObject * object )

qobject_cast() can also be used in conjunction with interfaces;

接口往往不是QObject的派生类,那么它又是如何工作的呢?比如:

Q_DECLARE_INTERFACE(MathInterface, "com.example.Plugin.MathInterface/0.1");

源码

#ifndef Q_MOC_RUN
#  define Q_DECLARE_INTERFACE(IFace, IId) \
    template <> inline const char *qobject_interface_iid<IFace *>() \
    { return IId; } \
    template <> inline IFace *qobject_cast<IFace *>(QObject *object) \
    { return reinterpret_cast<IFace *>((object ? object->qt_metacast(IId) : 0)); } \
    template <> inline IFace *qobject_cast<IFace *>(const QObject *object) \
    { return reinterpret_cast<IFace *>((object ? const_cast<QObject *>(object)->qt_metacast(IId) : 0)); }
#endif // Q_MOC_RUN
  • 对于接口类,此处特化了模板函数。使得 qobject_cast 对于接口类型可用。
  • 特化的函数调用了 qt_metacast 函数。但是,此处传递给qt_metacast函数的参数,不是类名,而是声明接口是用的IId
  • 注意,该宏被Q_MOC_RUN包围,moc预处理时,该宏不会简单地被展开(也不会单独为其生成什么代码)

Q_INTERFACES

像我们在插件学习二中所涉及到的,插件接口定义好以后,需要使用该接口的类需要使用 Q_INTERFACES 指定该接口。

不同于 Q_DECLARE_INTERFACE,Q_INTERFACES 是一个纯粹的需要moc进行处理的宏,moc解析其指定的接口,然后判断该接口是否已经使用 Q_DECLARE_INTERFACE 进行过声明。

最后moc生成包含元对象信息的文件,其中包括类似下面的内容:

void *Plugin1::qt_metacast(const char *_clname)
{
    if (!_clname) return 0;
    if (!strcmp(_clname, qt_meta_stringdata_Plugin1))
        return static_cast<void*>(const_cast< Plugin1*>(this));
    if (!strcmp(_clname, "MathInterface"))
        return static_cast< MathInterface*>(const_cast< Plugin1*>(this));
    if (!strcmp(_clname, "com.example.Plugin.MathInterface/0.1"))
        return static_cast< MathInterface*>(const_cast< Plugin1*>(this));
    return QObject::qt_metacast(_clname);
}

这样一来,qobject_cast 对与接口就可以正常工作了,而且不影响原来的QObject类型的转换动作包括inherits函数

QTBUG 20616

前几天刚好查些东西,并写了QObject派生类作为Qt 插件的Interface,看来这样用没什么问题啊,为什么会有bug!!??

刚看到这个bug时有点蒙,不过现在理解它应该没什么问题了吧? bug提交者给的例子很简单:

  • interfaceclass.h

#include <QObject>

class InterfaceClass : public QObject {
Q_OBJECT
public:
InterfaceClass() {}

};
Q_DECLARE_INTERFACE(InterfaceClass, "com.nokia.qt.broken.interfaceclass/-1");
  • main.cpp

#include "interfaceclass.h"
#include <QApplication>
#include <QDebug>
int
main(int argc, char **argv)
{
    QApplication app(argc, argv);

    InterfaceClass interfaceClass;
    qDebug() << "This pointer should not be \"QObject(0x0)\":"<<qobject_cast<InterfaceClass*>(&interfaceClass);

    return 0;
}

原因

将一个QObject派生类声明为接口(通过Q_DECLARE_INTERFACE),但是它并没有将其作为接口来使用(如果我们将接口都作为virtual类来使用,也就没这种问题了),而是作为一个普通的类来使用。

前面可知,Q_DECLARE_INTERFACE 特化了 qobject_cast,该特化后的 qobject_cast 需要 qt_metacast 的参与,而 qt_metacast 中接口相关的信息是通过 Q_INTERFACES 引入的。

这样一来,

  • 默认的 qobject_cast 本可以工作,但被屏蔽了
  • 特化的 qobject_cast 由于缺少信息,无法工作

如何解决

不知道官方最终会如何解决,个人觉得既然特化造成的问题,让特化后的函数保留原来的功能就可以了。

如果moc看到一个接口继承自QObject且含有Q_OBJECT,生成如下代码:

void *InterfaceClass::qt_metacast(const char *_clname)
{
    if (!_clname) return 0;
    if (!strcmp(_clname, qt_meta_stringdata_InterfaceClass))
        return static_cast<void*>(const_cast< InterfaceClass*>(this));
    if (!strcmp(_clname, "com.nokia.qt.broken.interfaceclass/-1"))
        return static_cast<InterfaceClass*>(const_cast<InterfaceClass*>(this));
    return QObject::qt_metacast(_clname);
}

而不是

void *InterfaceClass::qt_metacast(const char *_clname)
{
    if (!_clname) return 0;
    if (!strcmp(_clname, qt_meta_stringdata_InterfaceClass))
        return static_cast<void*>(const_cast< InterfaceClass*>(this));
(const_cast<InterfaceClass*>(this));
    return QObject::qt_metacast(_clname);
}

那么问题将可以解决。可是问题在于:

moc 如何知道该类被声明为了接口,Q_DECLARE_INTERFACE 可能和类定义并不再同一个文件内。需要为它自己引入一个 Q_INTERFACES 么?

还是说我们直接修改特化的 qobject_cast 函数,当转换失败时,用默认的方法再转换一次?

    template <> inline IFace *qobject_cast<IFace *>(QObject *object) \
    {IFace * face = reinterpret_cast<IFace *>((object ? object->qt_metacast(IId) : 0)); \
    return face ? face : static_cast<IFace*>(reinterpret_cast<IFace*>(object)->staticMetaObject.cast(object));}

参考


分享到:
评论

相关推荐

    Qt_TCP_Modbus例程

    程序应用是Qt5自带的modbus类进行设计,进行了十六进行寄存器数据传输,简单明了,包含TCP MOSBUS发送和接收数据,开发环境为ubuntu 16.04+Qt5.6.1,直接在Qt Creator中运行程序server程序会服一个TCP受保护的错误,...

    QT_CTP_MD接口的例子,包括了初始化,登录,订阅,还有非QOBJECT类和主UI之间的通信方法

    QT_CTP_MD接口的例子,包括了初始化,登录,订阅,还有非QOBJECT类和主UI之间的通信方法

    Qt一步一步实现插件通信(附源码)

    MyPluginInterface *plugin = qobject_cast*&gt;(loader-&gt;instance()); if (plugin) { connect(this, &MainWindow::taskGiven, plugin, &MyPluginInterface::setTask); connect(plugin, &MyPluginInterface::data...

    Qt源码分析之QObject

    Qt 源码分析之 QObject QObject 是 Qt 类体系的唯一基类,它是 Qt 各种功能的源头活水。因此,Qt 源码分析的第一节就放在这个 QObject 上。 首先,让我们来看一个简单的测试代码: ```c #include #include #...

    Qt_JS_Demo.zip

    【Qt_JS_Demo.zip】是一个压缩包,包含了一个基于Qt框架的应用程序,该程序展示了如何在Qt中嵌入网页,并实现Qt与JavaScript(JS)之间的交互。Qt是一个跨平台的C++图形用户界面应用程序开发框架,它允许开发者创建...

    untitled_Qt控件设计_QT_Untitled_

    QT是Qt Company开发的一种跨平台应用程序开发框架,广泛用于创建桌面、移动和嵌入式系统的图形用户界面。在“untitled_Qt控件设计_QT_Untitled_”这个项目中,初学者正试图通过实践熟悉Qt控件的使用,并进行简单的...

    migong.zip_qt 迷宫_qt走迷宫_qt迷宫_迷宫 QT_迷宫 qt代码

    QT提供了QObject的eventFilter()函数,可以用来过滤和处理键盘事件。当用户按下键盘上的方向键时,游戏逻辑会更新玩家的位置,并在屏幕上显示相应的变化。 计时功能通常需要用到QT的QTimer类,它提供了定时触发信号...

    qt.zip_QT好难_QT学习资料_QT开发难吗_c++ qt 难吗_qt好难啊

    Qt的许多类都是基于`QObject`类,它提供了信号与槽机制,这是Qt事件驱动编程的核心。通过信号与槽,开发者可以轻松地实现对象间的通信。例如,当一个按钮被点击时,可以触发一个信号,这个信号可以连接到槽函数,...

    QT.rar_QT 多线程_QT 多线程_QT线程_qt多线程_多线程qt

    QT是Qt库的缩写,它是一个开源的C++图形用户界面应用程序开发框架,由The Qt Company维护。在多核处理器和高性能计算需求日益增长的今天,多线程技术成为了软件开发中的重要组成部分,尤其在GUI(图形用户界面)应用...

    qss.rar_Qt qss_qt qss下载_qt qss 下载_qt setstysheet

    `setStyleSheet`是Qt中的一个函数,它属于QObject类,也被许多其他继承自QObject的类(如QWidget)重载。这个函数用于设置对象或整个应用程序的QSS样式表。通过调用这个函数,你可以一次性应用一套完整的样式规则,...

    Qt分析QObject子类内部成员结构

    在Qt框架中,`QObject`是所有用户界面和非用户界面组件的基础类,它提供了事件处理、信号与槽机制以及属性系统等核心功能。本文将深入探讨`QObject`子类的内部成员结构,以帮助初学者理解Qt的基石及其工作原理。 ...

    qt.rar_QT中的mediaPlayer_linux qt media play_qt 播放器_qt mediaPlaye

    QT通过`QObject::connect`函数实现事件监听和处理。 9. **源代码分析**:项目中提供的源代码可以作为学习如何在QT中使用`QMediaPlayer`和相关类的实例。初学者可以通过阅读和理解代码,掌握如何实现基本的播放控制...

    QtPluginDemo.rar

    ## No1.How to Create Qt Plugins ### 1、定义插件接口 ### 2、使用Q_DECLARE_INTERFACE()...### 4、qobject_cast()测试接口实现 ## No2.How to Debug Plugins ### 环境变量中设置QT_DEBUG_PLUGINS=1 ### 无法加载的

    QT5教程 完整版.zip_6QR_QT_QT5_qt 5_qt开发教程

    QT5教程 完整版.zip_6QR_QT_QT5_qt 5_qt开发教程,这个资源是一个关于QT5开发的详细教程,包含了丰富的实例和基础教学内容,适用于初学者和有一定经验的开发者。QT5是Qt库的一个重要版本,由The Qt Company提供,...

    QT 利用继承Qobject实现多线程

    在QT中,通过继承`QObject`类并利用`moveToThread()`函数,我们可以方便地实现多线程操作,这对于处理耗时任务或避免阻塞主线程来说至关重要。下面我们将深入探讨如何使用这些技术以及`QMutex`来确保线程安全。 ...

    QT帮助文档_中文版.zip

    2. **基本概念**:理解QT的主要组件,如QObject、QWidget、QApplication,以及它们之间的关系。 3. **信号与槽**:QT的核心机制,用于对象间的通信。了解如何定义信号和槽,以及如何连接它们以实现事件驱动的编程。...

    Qt之Q_PROPERTY学习

    `Q_PROPERTY`用于声明一个类的公有属性,使得这些属性能够被QML、Qt的信号与槽机制以及Qt的其他部分如`QObject::property()`函数所访问和操作。在本文中,我们将深入探讨`Q_PROPERTY`的使用方法,以及如何结合Qt的元...

    ThreadFromQThread_QT_qt多线程_QT多线程_源码.zip

    通过分析这些代码,我们可以深入理解QT多线程编程的细节,提高自己的编程技能。 总之,QT的QThread类为开发者提供了便捷的多线程编程接口,使我们可以轻松地在QT应用中实现并发处理。通过学习和实践这个源码,你...

    qt.rar_QT_Qt 翻译_Qt笔记

    这份笔记可能还包含了作者“嘒彼小星”的个人经验分享,比如在学习过程中遇到的问题及解决方案,或者是一些高级特性的深入探讨,如QML(Qt Meta Language)用于创建现代用户界面,以及并发和异步编程的策略。...

Global site tag (gtag.js) - Google Analytics