首页 Qt 在 Qt4 中使用 C++11

在 Qt4 中使用 C++11

10 3

原文出处:http://labs.qt.nokia.com/2011/05/26/cpp0x-in-qt/

我们前面介绍了许多 C++ 11 的优点,而且介绍了如何在 Qt 5 中使用 C++ 11。但是,Qt 5 毕竟只是一个尚未发布的版本,现在,我们要介绍的是,如何在 Qt 4 中使用 C++ 11。

现在,我们可以在 Qt 4.7 和 4.8 两个版本中使用 C++ 11。4.8 则增加了更多关于 C++ 11 的支持。

新的宏

如果编译器支持新的特性,Qt 提供了许多新的宏:

Q_COMPILER_RVALUE_REFS
Q_COMPILER_DECLTYPE
Q_COMPILER_VARIADIC_TEMPLATES
Q_COMPILER_AUTO_TYPE
Q_COMPILER_EXTERN_TEMPLATES
Q_COMPILER_DEFAULT_DELETE_MEMBERS
Q_COMPILER_CLASS_ENUM
Q_COMPILER_INITIALIZER_LISTS
Q_COMPILER_LAMBDA
Q_COMPILER_UNICODE_STRINGS

初始化器

Qt 4.8 为QVectorQListQStringList增加了新的构造函数,允许我们使用 C++ 11 提供的初始化器进行初始化。例如:

QVector<int> data {1, 2, 3, 4, 5};
QStringList ops = { QLatin1String("foo"), QLatin1String("bar") };

右值引用和移动语义

Qt 提供了许多隐式共享类。这意味着,如果你没有修改它们,那么复制操作将会是很高效的(写时复制)。这些操作对于std::vector是无效的,复制 std::vector 会复制所有数据。如果,如果你的代码类似这样:

std::vector<int> m_foo;
// ...
m_foo = getVector();

getVector()函数可能需要构造一个新的std::vector,将其复制给一个临时变量,然后std::vector::operator=运算符则需要销毁旧的m_foo,再将这个临时变量中的全部数据复制到m_foo。当这条语句结束时,这个临时的 vector 将会被销毁,它的析构函数会删除其所有数据。如果operator=简单地将m_foo的数据切换到这个临时变量的数据,无疑会使这个操作更高效。这样的话,m_foo的旧数据要在这个临时变量销毁时才去 delete,这就减少了不必要的复制。这就是 C++ 11 的移动语义,它是由右值引用实现的。

即使复制隐式共享的类代价并不昂贵,但也并不意味着没有代码,我们依然要增加和减少引用计数,调用operator=也不能是 inline 的(因为它得访问 private 数据,为了二进制兼容,我们不能将其写作 inline)。下面,来看看 Qt 4.8 的QImage移动运算符:

#ifdef Q_COMPILER_RVALUE_REFS
    inline QImage &operator=(QImage &&other)
    {
        qSwap(d, other.d);
        return *this;
    }
#endif

我们仅仅是交换了两个图像的内部数据,比起通常的操作,这样的操作已经很廉价了。在 Qt 中,大部分隐式共享类都是这么做的。既然所有容器都有大量的这种操作,那么将operator=按照移动语义定义,肯定会提升 Qt 的性能。这也是使用 C++ 11 编译 Qt 的理由之一。

foreach循环

Qt 提供了一个非常方便的foreach宏。你也可以在其它 C++ 库,比如 boost 中找到类似的东西。Qt 的foreach仅仅是一个比较复杂的宏,C++ 11 则做得更多,将其作为语言的一部分。因此,像下面的代码:

foreach(const QString &option, optionList) { ... }

我们就可以写成

for(const QString &option : optionList) { ... }

这二者还是有一点区别的:Qt 在遍历之前将容器进行了复制。对于 Qt 容器,这是廉价操作,因为 Qt 容器都是隐式共享的,但对于标准库的容器,这样做会引发对整个内容的深拷贝。C++ 11 的foreach循环不需要复制。这意味着,如果你在遍历过程中添加或者删除了容器的元素,所带来的后果是无定义的。

如果容器是共享的,并且不是const的,那么,如果你要支持 C++ 11 的新的 for,应该自己去调用容器的begin()end()函数,将容器复制一遍。因此,正确的代码应该是这样的:

template<class T>
const T &const_(const T &t)
{
    return t;
}

for(auto it : const_(vector)) { ... }

Lambda 表达式

又到了我最喜欢的部分了 ;-P

比起 Qt 5 来,Lambda 表达式在 Qt 4 中应用不是很多,仅仅是在 QtConcurrent 的某些函数中。

我们现在可以在QtConcurrent::run()QtConcurrent::map()中使用 Lambda 表达式:

QList<QImage> images = ...
QFuture<void> future = QtConcurrent::map(images, [](QImage &image) {
          image = image.scaled(100,100);
      });

现在开始使用!

如果你正在使用 MSVC 2010,那就没什么好做的了。你完全可以开始使用 C++ 11 的新特性,比如 Lambda 表达式和右值引用。

如果你使用的是 GCC,你需要增加-std=c++0x编译参数,然后在 .pro 文件中添加:

QMAKE_CXXFLAGS += -std=c++0x

如果你需要使用 C++ 11 编译 Qt,那么就使用:

CXXFLAGS="-std=c++0x"
./configure

Qt 将会以 C++ 11 编译,同时兼容旧的 C++ 代码。另外需要说明的是,如果你仅仅使用 C++ 11 编译自己的程序,是不需要使用 C++ 11 重新编译 Qt 的。

10 评论

smarthand 2012年6月25日 - 10:21

😛
在51CTO上找到了你的博客,谢谢你的Qt入门文章,我也是买了《C++ GUI Qt4编程第二版》学习,结果正如你所说,看得发晕,再次感谢你的好入门文章。感谢你的分享精神,帮我们解决了很多问题,让我们的Qt学习走了捷径,谢谢!

回复
DevBean 2012年6月25日 - 11:32

不用客气,大家共同学习!

回复
liuyanghejerry 2012年6月27日 - 16:25

这是篇翻译,但是没有出处不好吧。

出处貌似是:http://labs.qt.nokia.com/2011/05/26/cpp0x-in-qt/

回复
DevBean 2012年6月27日 - 16:47

是的,忘记加上出处了,谢谢提醒!

回复
nandaoruguo 2013年3月14日 - 10:40

我来赞下,我对比原文,楼主翻译的好滴
感谢~学习了不少

回复
豆子 2013年3月14日 - 15:06

多谢!

回复
Jakes 2013年5月30日 - 19:13

我在Qt5下使用基于范围的for循环尝试遍历QMap的对象出错,错误信息:
error: conversion from 'const QString' to non-scalar type 'QMap::const_iterator' requested
我写的for形式:
for(decltype(qmap.cbegin()) citer : qmap ){}
为什么会出现这种问题呢?貌似编译器把需要的类型解释成了QString,而不是迭代器或是QPair什么的。

回复
Jakes 2013年5月30日 - 19:16

怎么角括号被过滤了,我定义的类型是

QMap qmap;

回复
Jakes 2013年5月30日 - 19:16

怎么角括号又被过滤了,我定义的类型是
QMap[QString,QString] qmap;
真心不是故意的。。。 🙁

回复
豆子 2013年6月6日 - 14:49

qmap 是 QMap<QString,QString> 类型的,cbegin() 返回值是 QMap::const_iterator 类型,二者类型不匹配。实际上 foreach 循环是没有这样去写的。如果使用 foreach,最好是 for(auto e : qmap) 这种写法,然后用 e.first 和 e.second 获取值。

PS:尖括号会自动转义,使用HTML转义字符即可。

回复

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2