logo资料库

Qt4迁移至Qt5完全指南.pdf

第1页 / 共5页
第2页 / 共5页
第3页 / 共5页
第4页 / 共5页
第5页 / 共5页
资料共5页,全文预览结束
Qt4 迁移至 Qt5 将 Qt 4 代码迁移到 Qt 5 还是比较简单的。实际上,在 Qt 5 开发过程中就 已经注意了与 Qt 4 代码保持兼容性。 与 Qt 3 到 Qt 4 的迁移不同,Qt 5 的核心类库并没有做大的 API 的修改, 只有几个新的类取代了旧的(例如,像 Qt 4 的 QList 取代了 QPtrList 和 QValueList;itemview 取代了 Q3ListView;graphicsview 取代了 Canvas API); 同时也没有那些编译通过了,但运行时的行为却与之前的不一致的(例如, QWidget::show 现在是非虚函数,绘制应该在 paintEvent 中进行等等)。 但是,迁移的代价也不会是零。本文总结了 KDE 部分代码从 Qt 4 迁移到 Qt 5 所需要注意的问题。 KAddressbook 迁移到 Qt 5 和 KDE Frameworks 5 KDE PIM 是最后一个完整迁移到 Qt 4 和 kdelibs 4 的部分。迁移到 Qt 5 应 当更快些。 1.迁移之前 迁移策略中应该有这么一条:能够同时使用新版本和旧版本的 Qt 编译代码, 也就是保持 Qt 4 和 Qt 5 的兼容性。这么做的好处是,能够保证你的代码在最小 化的库上可以通过编译,让你的代码在 Qt 4 依然可用;也能够保证在迁移过程 中,单元测试代码能够顺利运行;最后,还能够很快地区别出,哪些是本来就有 的 bug,哪些是由于迁移到 Qt 5 新引入的 bug。 1.1 迁移 Qt3Support 迁移代码,可以从让当前 Qt 4 代码“现代化”开始。 从代码迁移角度来看,Qt 5 的有意义的改变是,移除了 Qt3Support 模块, 移除了所有标记为 Qt3Support 的 API。在大多数情况下,Qt3Support 的代码在 Qt 4 中有一个更适合的名字。有的函数直接改名,例如 QWidget::setShown 改 为 QWidget::setVisible。部分 KDE 代码仍然使用了旧的函数,这种情况也发生 在其它古老的第三方代码库中。 从 Qt 4 迁移到 Qt 5,移除代码中的 Qt3Support API 是必要的、不可避免 的。虽然理论上,我们也可以为 Qt 5 单独编译 Qt3Support 模块。 1.2 修正 include 相对于 Qt 4,Qt 5 的一个主要的基础架构修改是,将 widget 从 QtGui 模块 剥离开来,放到了全新的 QtWidgets 模块。这显然需要改变构建系统,同时也要 求新引入一些原本不需要单独引入的头文件,因为这些头文件可能从现有 QtGui 模块中删除了。举个例子,Qt 5 中我们需要添加#include ,这在之前 的 Qt 4 的代码是不需要的。这是因为,在 Qt 4 中,它已经被引入到 gui/kernel/qevent.h 头文件,但是 Qt 5 则没有。 另外一个有关 include 的修正是,你必须将之前的 QtGui 模块的头文件改成 QtWidgets,例如,
#include 在 Qt 5 中应该写成 #include 为了避免更多的修改,我的建议是,使用下面这种更具可移植性的写法(这 种写法在 Qt 4 和 Qt 5 中同样适用): #include 我们可以编写一个简单的脚本来执行这个枯燥的操作。当然,你也可以利用 IDE 提供的批量替换功能。就像清理 Qt3Support 一样,修正 include 的工作也 应该在真正的迁移之前完成。 1.3 修正平台相关的定义 许多 Qt 和 KDE 程序都会有特定平台的代码。预处理器需要使用特定的宏, 而在 Qt 5 中,所有的 Q_WS_*都变成了 Q_OS_*。例如,在 Qt 4 中的代 码 #ifdef Q_WS_WIN // call windows API #endif 在 Qt 5 中应该写成 #ifdef Q_OS_WIN // call windows API #endif Qt 5 移除了 Q_WS_宏,所以所有包含了这些宏的代码都不会通过编译。这些 代码(例如,特定操作系统,而不是特定窗口系统的代码)所包围的宏都应该改 成 Q_OS_。 1.4 丢失 Q_OBJECT 宏以及清理 metatype Qt 4 中很容易忘记在需要的地方添加 Q_OBJECT 宏,这会导致某些不可预见 的运行时 bug。这些在 Qt 5 中也是类似的,但是如果你通过使用 Q_DECLARE_METATYPE 宏,将一个 QObject 子类的指针保存到 QVariant 时,你会 得到一个编译错误(Qt 4 中也会有编译错误,但 Qt 4 的错误与 Qt 5 不同)。这 是因为,QVariant 现在需要保存 QObject 子类的指针(确切的子类,强类型指 针),这是 QtDeclarative、语言绑定以及严重依赖 QMetaObject 内省 API 的程 序所需要的新的特性。 另外一个影响是,Q_DECLARE_METATYPE 宏的参数必须是完整定义,而不能 是前向声明。因此,下面的代码是不能通过编译的: class MyType; Q_DECLARE_METATYPE(MyType); 这个宏现在必须放到 MyType 完整定义的地方去(例如定义它的头文件)。另 外,如果 MyType 继承自 QObject,这个宏就可以完全删除。 1.5 重构 Qt 5 最大的变化之一是更加注重 QML(一个运行时解释语言,用于创建用户 界面)和 QtQuick(语言相关的 API)。尽管 QtWidgets 依然可用,迁移到 QML
可能获得更好的性能和用户交互特性。 QML 是运行时解释型的,不像 C++那样具有类型安全的限制,它适合于结合 使用 QObject 子类表达的数据模型,这种数据的属性以及其它类型信息都可以由 QVariant 包含。 如果迁移到 Qt 5 的目的之一是增加 QML 的使用量,那么,你就应该注意重 构已有代码,让业务逻辑和数据模型(也就是应用程序的状态表示以及数据内容) 分离。这种重构可以基于 Qt 4 的代码。我们甚至可以基于 Qt 4 提供一个正常工 作的或者是试验用的 QML 移植,来验证我们的概念。这是 Qt 4 就提供了 QML 的 原因之一,我们可以把它看成是 Qt5Support。 1.6 移除 QWS QWS 系统不再是 Qt 5 的一部分,它的 API 也已经被移除。使用了这些 API 的代码应该移植到新的 QPA 系统,这是 Qt 5 的核心部分之一。QPA 实际上在 Qt 4.8 就已经引入(Qt 5 的 API 可能有些许不同)。 所以说,现在也可以将代码直接迁移到 Qt 4 的 QPA,当然,以后我们还得 再迁移到 Qt 5,但是整体思路并不会发生重大改变。关键在于,现在没有什么 有关 Qt 4.8 的 QPA 文档,只能比较 Qt 5 的文档做相应的处理。 2.迁移 如果你已经按照前面的步骤来到了这一步,那么就意味着你的代码可以完全 兼容 Qt 4,也可以把眼光放到 Qt 5 上。一些 API 在 Qt 5 中不是源代码兼容的, 这些大部分在变更日志中可以找到。大多数情况下,对于“通常的”代码,这些 都不是问题,因为这些修改的部分很少用到,或者是仅在边缘条件下有所改变。 不管如何,这些改变都需要在迁移中进行处理。它们构成了 Qt 4 和 Qt 5 实际的区别,它们会强迫你放弃对 Qt 4 的支持,或者是使用#ifdef 预处理宏来 兼容其它版本。 2.1 插件加载 另外一个迁移问题是,在大量使用插件的系统中,插件部分的用户代码需要 改变。Qt 5 中,moc 用于负责生产插件元数据,所以,不同于 Qt 4 中仅仅需要 在 C++文件中添加一个预处理宏 Q_EXPORT_PLUGIN2,Qt 5 需要在头文件中添加 一个新的宏,以便 moc 能够处理。 这个过程还是比较直观的。但是,问题在于,如果 Q_EXPORT_PLUGIN2 宏被 包裹在另外一个宏中,类似 KDE 的 K_EXPORT_PLUGIN,那么这种处理就必须修改, 因为 Qt 5 的 moc 不能处理这种宏的嵌套(moc 不做完整的预处理)。 2.2 迁移单元测试 前面我们提到,有些代码不是源代码兼容的,这其中就包括 QTestLib 模块 ——对于 QSKIP 宏的改变。Qt 4 中,这个宏有两个参数,但是在 Qt 5 则只有一 个。 这造成了一个明显的迁移问题。KDE 对此的解决方案是,创建一个包装宏, 接受两个参数,而对于 Qt 5 则舍弃一个。这应该是在未来需要放弃的,因为我
们的程序不应该在将来还对 Qt 4 作一定的支持。也许这个“未来”会相当的遥 远。 另外一个解决方案是,使用 C99 和 C++ 11。如果你使用了-std=c++11 进行 编译,那么就不会有这个问题。 2.3 QMetaMethod::signature 在某些严重依赖于 QMetaObject 内省系统的程序中,经常会使用 QMetaMethod::signature API。在 Qt 4,这个函数返回 const char *。而在 Qt 5,其返回值是动态构建的,所以需要进行一定的修改(因为不能返回局部变量 的指针)。现在,等价的函数有一个不同的返回值(QByteArray),以及不同的名 字(QMetaMethod::methodSignature)。仅仅改变函数名,会有运行时错误: // 旧的 Qt 4 代码 // 返回的 const char * 赋值给本地变量,然后直接使用 const char *name = mm.signature(); otherApi(name); // 新的 Qt 5 代码 const char *name = mm.methodSignature(); // name 会成为野指针! // methodSignature() 返回的是 QByteArray,在函数返回时已经析构 otherApi(name); // 等着程序崩溃吧! 这个运行时 bug 可以通过在构建时添加 QT_NO_CAST_FROM_BYTEARRAY 来避免。 这应该是在 Qt 4 就应该启用的。这个函数名的改变以及返回值的变化应该在迁 移工作的一个独立步骤中修正。 2.4 改变的 virtual 函数 Qt 5 中,少量 virtual 函数有了变化。这不会在迁移时构成编译错误(除 非纯虚函数的改变),但是会在运行时引发异常(因为重写的函数不能被执行)。 其中一个是,QAbstractItemView::dataChanged 信号有了一个参数。 我们有许多方法来解决这个问题。新的 C++ 11 标准有一个语法,能够指示 一个类的特定函数是不是对父类同名函数的一个覆盖。使用这个语法,我们可以 在迁移时使其成为编译错误。编译错误总是要比运行时错误好,因为它们更容易 找到(事实上,它们是不能回避的)。 另外一种方法是,开启编译器警告。GCC 可以对子类的函数隐藏了父类的虚 函数提出警告(-Woverloaded-virtual)。我们可以在构建之前开启这个特性, 这样就可以更加明显地找到问题。另外,始终采用最严格的编译器警告级别当然 是值得推荐的做法,因此你应该将这个警告添加到你的构建系统的参数中。
3.迁移之后 所有的程序都是要维护的,而不仅仅是编写或者迁移完这么简单。当你把 你的程序迁移到 Qt 5 之后,还有更多的步骤值得你去做。 我们不应该在同一个分支同时提供对 Qt 4 和 Qt 5 的支持。在结束了整个迁 移之后,单元测试也能够全部通过,这就意味着,我们可以开始两个分支:基于 Qt 4 的和基于 Qt 5 的。原因之一就是能够使用那些在 Qt 5 废止了的 Qt 4 的 API。 3.1 移除对标记为废弃的函数的调用 标记为 deprecated 的函数在 Qt 5 中还是存在的,但是可能会在未来消失。 这种情况是 Qt 5 为了与 Qt 4 抱持兼容而保留的,就像 Qt 4 保留了 Qt 3 的支持 一样。 我们应该尽快移除 deprecated 函数,因为它们会在编译时发出警告,这会 与其它有用的警告混杂在一起,而新的 API 可能更快,可能用起来更方便。 3.2 迁移到 QML 将已有的 UI 迁移到 QML 是可选的步骤,可以在迁移之前完成,也可以在迁 移之后进行。 Qt 4 提供的 QML 版本,在 Qt 5 中依然适用,名字为 QtQuick1。不过,这仅 供迁移用,以后也不会进行性能提升等。使用了这些的代码应该再迁移到 QML2 (以及 Qt 5 中的 QtQuick API),以获得更好的性能。迁移到 QML2 更像是使用 另外名字的 C++ API,同时要修改绘制自定义组件的方法。因为 QML2 使用的是 scene-graph 机制,而不是 QPainter API(正是由于这个原因,才会导致性能的 提升),自定义组件需要使用不同的 更新 API 进行绘制。 4.结论 前面所说的从 Qt 4 迁移到 Qt 5 的步骤不是要求严格遵守的。在 Qt 5 正式 发布之后,应该会有一个更加完整的列表。 另外需要注意的是,这篇文章关注的是(另人厌烦的)那些使用 Qt 5 构建 应用程序所需要的步骤。然而关于升级时出现的运行时 bug 只字未提,而这才是 迁移时真正花费时间的。目前一些 KDE 应用程序出现了一些微妙的 bug 急需处 理: Dolphin About 页面出现编码问题 Konqueror 菜单有小 bug 在变更日志中列出的边界条件的更改暗示了那些在 Qt 5 中有所不同的部分 可能会在迁移代码中引发 bug。
分享到:
收藏