将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所需要注意的问题。

KDE PIM是最后一个完整迁移到Qt 4和kdelibs 4的部分。迁移到Qt 5应当更快些。

迁移之前

迁移策略中应该有这么一条:能够同时使用新版本和旧版本的Qt编译代码,也就是保持Qt 4和Qt 5的兼容性。这么做的好处是,能够保证你的代码在最小化的库上可以通过编译,让你的代码在Qt 4依然可用;也能够保证在迁移过程中,单元测试代码能够顺利运行;最后,还能够很快地区别出,哪些是本来就有的bug,哪些是由于迁移到Qt 5新引入的bug。

迁移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模块。

修正include

相对于Qt 4,Qt 5的一个主要的基础架构修改是,将widget从QtGui模块剥离开来,放到了全新的QtWidgets模块。这显然需要改变构建系统,同时也要求新引入一些原本不需要单独引入的头文件,因为这些头文件可能从现有QtGui模块中删除了。举个例子,Qt 5中我们需要添加#include <QDrag>,这在之前的Qt 4的代码是不需要的。这是因为,在Qt 4中,它已经被引入到gui/kernel/qevent.h头文件,但是Qt 5则没有。

另外一个有关include的修正是,你必须将之前的QtGui模块的头文件改成QtWidgets,例如,

#include <QtGui/QWidget>

在Qt 5中应该写成

#include <QtWidgets/QWidget>

为了避免更多的修改,我的建议是,使用下面这种更具可移植性的写法(这种写法在Qt 4和Qt 5中同样适用):

#include <QWidget>

我们可以编写一个简单的脚本来执行这个枯燥的操作。当然,你也可以利用IDE提供的批量替换功能。就像清理Qt3Support一样,修正include的工作也应该在真正的迁移之前完成。

修正平台相关的定义

许多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_*。

丢失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,这个宏就可以完全删除。

重构

Qt 5最大的变化之一是更加注重QML(一个运行时解释语言,用于创建用户界面)和QtQuick(语言相关的API)。尽管QtWidgets依然可用,迁移到QML可能获得更好的性能和用户交互特性。

QML是运行时解释型的,不像C++那样具有类型安全的限制,它适合于结合使用QObject子类表达的数据模型,这种数据的属性以及其它类型信息都可以由QVariant包含。

如果迁移到Qt 5的目的之一是增加QML的使用量,那么,你就应该注意重构已有代码,让业务逻辑和数据模型(也就是应用程序的状态表示以及数据内容)分离。这种重构可以基于Qt 4的代码。我们甚至可以基于Qt 4提供一个正常工作的或者是试验用的QML移植,来验证我们的概念。这是Qt 4就提供了QML的原因之一,我们可以把它看成是Qt5Support。

移除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的文档做相应的处理。

迁移

如果你已经按照前面的步骤来到了这一步,那么就意味着你的代码可以完全兼容Qt 4,也可以把眼光放到Qt 5上。一些API在Qt 5中不是源代码兼容的,这些大部分在变更日志中可以找到。大多数情况下,对于“通常的”代码,这些都不是问题,因为这些修改的部分很少用到,或者是仅在边缘条件下有所改变。

不管如何,这些改变都需要在迁移中进行处理。它们构成了Qt 4和Qt 5实际的区别,它们会强迫你放弃对Qt 4的支持,或者是使用#ifdef预处理宏来兼容其它版本。

插件加载

另外一个迁移问题是,在大量使用插件的系统中,插件部分的用户代码需要改变。Qt 5中,moc用于负责生产插件元数据,所以,不同于Qt 4中仅仅需要在C++文件中添加一个预处理宏Q_EXPORT_PLUGIN2,Qt 5需要在头文件中添加一个新的宏,以便moc能够处理。

这个过程还是比较直观的。但是,问题在于,如果Q_EXPORT_PLUGIN2宏被包裹在另外一个宏中,类似KDE的K_EXPORT_PLUGIN,那么这种处理就必须修改,因为Qt 5的moc不能处理这种宏的嵌套(moc不做完整的预处理)。

迁移单元测试

前面我们提到,有些代码不是源代码兼容的,这其中就包括QTestLib模块——对于QSKIP宏的改变。Qt 4中,这个宏有两个参数,但是在Qt 5则只有一个。

这造成了一个明显的迁移问题。KDE对此的解决方案是,创建一个包装宏,接受两个参数,而对于Qt 5则舍弃一个。这应该是在未来需要放弃的,因为我们的程序不应该在将来还对Qt 4作一定的支持。也许这个“未来”会相当的遥远。

另外一个解决方案是,使用C99和C++ 11。如果你使用了-std=c++11进行编译,那么就不会有这个问题。

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就应该启用的。这个函数名的改变以及返回值的变化应该在迁移工作的一个独立步骤中修正。

改变的virtual函数

Qt 5中,少量virtual函数有了变化。这不会在迁移时构成编译错误(除非纯虚函数的改变),但是会在运行时引发异常(因为重写的函数不能被执行)。其中一个是,QAbstractItemView::dataChanged 信号有了一个参数。

我们有许多方法来解决这个问题。新的C++ 11标准有一个语法,能够指示一个类的特定函数是不是对父类同名函数的一个覆盖。使用这个语法,我们可以在迁移时使其成为编译错误。编译错误总是要比运行时错误好,因为它们更容易找到(事实上,它们是不能回避的)。

另外一种方法是,开启编译器警告。GCC可以对子类的函数隐藏了父类的虚函数提出警告(-Woverloaded-virtual)。我们可以在构建之前开启这个特性,这样就可以更加明显地找到问题。另外,始终采用最严格的编译器警告级别当然是值得推荐的做法,因此你应该将这个警告添加到你的构建系统的参数中。

迁移之后

所有的程序都是要维护的,而不仅仅是编写或者迁移完这么简单。当你把你的程序迁移到Qt 5之后,还有更多的步骤值得你去做。

我们不应该在同一个分支同时提供对Qt 4和Qt 5的支持。在结束了整个迁移之后,单元测试也能够全部通过,这就意味着,我们可以开始两个分支:基于Qt 4的和基于Qt 5的。原因之一就是能够使用那些在Qt 5废止了的Qt 4的API。

移除对标记为废弃的函数的调用

标记为deprecated的函数在Qt 5中还是存在的,但是可能会在未来消失。这种情况是Qt 5为了与Qt 4抱持兼容而保留的,就像Qt 4保留了Qt 3的支持一样。

我们应该尽快移除deprecated函数,因为它们会在编译时发出警告,这会与其它有用的警告混杂在一起,而新的API可能更快,可能用起来更方便。

迁移到QML

将已有的UI迁移到QML是可选的步骤,可以在迁移之前完成,也可以在迁移之后进行。

Qt 4提供的QML版本,在Qt 5中依然适用,名字为QtQuick1。不过,这仅供迁移用,以后也不会进行性能提升等。使用了这些的代码应该再迁移到QML2(以及Qt 5中的QtQuick API),以获得更好的性能。迁移到QML2更像是使用另外名字的 C++ API,同时要修改绘制自定义组件的方法。因为QML2使用的是scene-graph机制,而不是QPainter API(正是由于这个原因,才会导致性能的提升),自定义组件需要使用不同的 更新API进行绘制。

结论

前面所说的从Qt 4迁移到Qt 5的步骤不是要求严格遵守的。在Qt 5正式发布之后,应该会有一个更加完整的列表。

另外需要注意的是,这篇文章关注的是(另人厌烦的)那些使用Qt 5构建应用程序所需要的步骤。然而关于升级时出现的运行时bug只字未提,而这才是迁移时真正花费时间的。目前一些KDE应用程序出现了一些微妙的bug急需处理:

在变更日志中列出的边界条件的更改暗示了那些在Qt 5中有所不同的部分可能会在迁移代码中引发bug。

QT-【转】Qt 4迁移至Qt 5的更多相关文章

  1. Qt入门(1)——初识Qt

    Qt是一个跨平台C++图形用户界面应用程序开发框架.它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器.Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta ...

  2. qt之fillder抓包(QT网络版有一些具体的坑)

    最近项目中使用到了Qt的网络库,在用的过程中也发现了不少坑和问题,本文仅仅作为记录,方便日后查阅.    因为我们整个客户端的gui都是使用qt来完成的,心想qt既然有网络库,而且真心觉着qt封装的控 ...

  3. Qt信号槽的一些事 Qt::带返回值的信号发射方式

    一般来说,我们发出信号使用emit这个关键字来操作,但是会发现,emit并不算一个调用,所以它没有返回值.那么如果我们发出这个信号想获取一个返回值怎么办呢? 两个办法:1.通过出参形式返回,引用或者指 ...

  4. QT源码解析(七)Qt创建窗体的过程,作者“ tingsking18 ”(真正的创建QPushButton是在show()方法中,show()方法又调用了setVisible方法)

    前言:分析Qt的代码也有一段时间了,以前在进行QT源码解析的时候总是使用ue,一个函数名在QTDIR/src目录下反复的查找,然后分析函数之间的调用关系,效率实在是太低了,最近总结出一个更简便的方法, ...

  5. QT入门学习笔记2:QT例程

    转至:http://blog.51cto.com/9291927/2138876 Qt开发学习教程 一.Qt开发基础学习教程 本部分博客主要根据狄泰学院唐老师的<QT实验分析教程>创作,同 ...

  6. Qt、Qte与Qtopia(Qt嵌入式的发展历程)

    Qt的授权是分为两条线,商业版和开源版.如果使用商业版的Qt,那么开发出的程序可以是私有的和商业的:如果使用的是开源版的Qt,由于其使用的是GPL协议,那么可发出的程序也必须是GPL的.不过自从qt ...

  7. Qt - QDialog,QWidget实现模态及非模态(模态Widget不能有父窗口,如果设置无边框就不能阻塞父窗口,但是可以强行设置指定Qt::Dialog,还可以setAttribute(Qt::WA_ShowModal),很多讲究)good

    在Qt中QDialog为“窗口”,而QWidget为“部件”,首先还是了解下<Qt 窗口与部件的概念>. 对于 QDialog 的模态及非模态是直接可以实现的,很多课本中都会提到,此处总结 ...

  8. Qt文档系统分析(解释Qt文档的生成工具与过程)

    写在前面 只要打开Qt Assistant或Qt Creator的Help,或是打开在线版的 http://doc.qt.nokia.com ,Qt的漂亮的文档就会呈现在我们眼前.而且 Qt的文档,长 ...

  9. Qt and C++ Reflection,利用Qt简化C++的反射实现

    如何在C++中实现反射机制,应该算是C++开发中经常遇到的问题之一.C++程序没有完整的元数据,也就无法实现原生的反射机制.从性能的角度讲,这样的设计不难理解,毕竟在运行时储存这些元数据需要额外的开销 ...

随机推荐

  1. LR_问题_在导入wsdl时出现parsing error

    问题描述:使用LR录制webservice协议的脚本,在导入wsdl时出现parsing error,详见图 问题解决:在导入wsdl时输入的地址错误,只指定了地址的虚拟目录名称,未指定方法名称,应该 ...

  2. 客户端一个http连接包含两个方向,一个是这个http连接的输入,另一个是这个http连接的输出。

    1.客户端一个http连接包含两个方向,一个是这个http连接的输入,另一个是这个http连接的输出. 利用httpclient进行ip地址和端口号连接后,http的输出端作为http请求参数设置.h ...

  3. C# 时间函数

    DateTime dt = DateTime.Now; string str = dt.ToString("yyyy-MM-dd");//2013-09-07 str = dt.T ...

  4. Spring两种实现AOP的方式

    有两种实现AOP的方式:xml配置文件的方式和注解的形式 我们知道通知Advice是指对拦截到的方法做什么事,可以细分为 前置通知:方法执行之前执行的行为. 后置通知:方法执行之后执行的行为. 异常通 ...

  5. C++:类的组合

    在类中定义的数据成员一般都是基本数据类型或服复合数据类型.但是还可以根据需要使用 其他类的对象作为正在声明的数据成员.复杂的对象还可以有比较简单的对象一某种方式组合 而成,复杂对象和组成它的简单对象之 ...

  6. PCL—低层次视觉—点云分割(超体聚类)

    1.超体聚类——一种来自图像的分割方法 超体(supervoxel)是一种集合,集合的元素是“体”.与体素滤波器中的体类似,其本质是一个个的小方块.与之前提到的所有分割手段不同,超体聚类的目的并不是分 ...

  7. JSF开篇之Login案例

    开发环境:Myeclipse+JDK5+MyEclipse Tomcat+jsf2.2.8 JSF看起来和STRUTS还是有些像的,刚开始还是遇到一点问题:资源包的存放路径及文件访问路径. 开发Log ...

  8. 成为一个PHP专家:缺失的环节

    这一篇文章是“Becoming a PHP Professional”系列 4 篇博文中的第 1 篇. 当浏览各类与PHP相关的博客时,比如Quora上的问题,谷歌群组,简讯和杂志,我经常注意到技能的 ...

  9. eclipse jetty插件安装(离线版)

    按照网上的说法安装jetty插件,run-jetty-run,google那个网址根本链接不上.所以插件装不上,网上搜索本地版,试了几个都不好使,功能不全,这是因为下载的本地资源jar包不全导致,好坑 ...

  10. IOSTimer的例子留个备注

    1.创建一个定时器 ,以下是便利构造器方法,+ scheduledTimerWithTimeInterval:invocation:repeats:+ scheduledTimerWithTimeIn ...