原文链接:Olivier Goffart - C++0x in Qt
虽然大家都如此地热衷于QML和javascript技术,我们几个仍在用C++进行编码 ;-)。C++即将迎来一次升级:C++11(以前被称为C++0x)。最终草案在去年三月获得了C++标准委员会的批准,而最终规范预计将在这个夏天发布。如果你尚不了解它,我建议你阅读专门的页面,比如维基百科或C++0x FAQ。
C++0x的目标之一是使这个语言更易用。但这几乎是不可能的,因为它只会增加更多需要学习的东西。我们可以寄希望于C++中“最常用”的子集会更加直观和易用。
好消息是毋须苦苦等待,你今天就可以开始使用它。你可能已经使用了支持C++0x部分特性的编译器:GCC或MSVC 2010。
尽管你已经可以在老版本的Qt(比如Qt4.7)中使用C++0x,但Qt 4.8将为C++0x提供更多支持:
新加的宏
如果编译器支持新的特性,一些宏将被定义
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
初始化列表(Initializer lists)
Qt 4.8为QVector、QList以及QStringList添加了新的构造函数,这使得你可以使用大括号初始化器(bracket initializer)来初始化它们。你现在可以通过
QVector<int> testsData { 1, 2, 10, 42, 50, 123 };
QStringList options = { QLatin1String("foo"), QLatin1String("bar") };
使用元素来直接初始化这些容器。
右值引用和移动语义
Qt中的大部分类是隐式共享的,这意味着只要你不对其修改,单纯复制它们将很有效率(写时复制)。但对std::vector却不是这样,每一次复制都将拷贝所有的数据。
所以,如果你有如下的代码
std::vector<int> m_foo;
...
m_foo = getVector();
getVector可能会构造一个新的std::vector,并将其作为一个临时对象返回,然后std::vector::operator=将删除m_foo原有内容,并将临时vector的所有内容复制到m_foo。在语句结束时,临时vector将被析构,而且析构函数将删除其所有数据。相反,如果operator=是将m_foo与临时vector的数据进行交换,这将会更有效率。这样一来,m_foo的原有数据将会在临时对象析构时被删除,并且不需要无用的拷贝。这就是C++0x中的移动语义,它需要通过右值引用来实现。
尽管复制一个隐式共享类的代价很低,但也不是免费午餐。我们仍然必须增加和减少引用计数,而且由于需要访问私有数据,对operator=的调用也不能被内联。(为了二进制兼容性我们不能内联它)。
所以,现在看看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++0x支持来编译Qt的原因。
基于序列的for循环(Range-based for-loop)
Qt已经提供了一个便利的foreach
,在其他一些库,比如Boost中你也可以找到类似的东西。
Qt的foreach借助复杂的宏来工作,但C++0x更进了一步,使之成了语言的一部分。所以,不必写
foreach(const QString &option, optionList) { ... }
你可以写
for (const QString &option : optionList) { ... }
二者有点细微的区别:Qt在迭代之前拷贝了容器。由于使用了隐式共享,对于Qt容器的拷贝代价是低廉的,但是对于需要将所有内容进行深度拷贝的标准容器来说代价却是昂贵的。
C++0x基于序列的for不需要拷贝,但这也意味着,在迭代容器时,添加或删除元素的行为将是未定义的。
新的for将会调用begin()
和end()
函数,对于共享和非const的Qt容器,这将导致一个拷贝。因此,最好传递const容器。
template<class T> const T &const_(const T &t) { return t; }
for(auto it : const_(vector)) { ... }
Lambda
我们使用QtConcurrent中的一些函数对lambda做了测试。我们在QtConcurrent::run
和QtConcurrent::map
中进行了测试。
但是,对于QtConcurrent::mapped
尚不工作,我们以后可能会修复它。
所以,QtConcurrent::map文档中的scale例子可以被重写成这样
QList<QImage> images = ...
QFuture<void> future = QtConcurrent::map(images, [](QImage &image) {
image = image.scaled(100,100);
});
我也对信号和槽连接中的lambda使用做了些研究,但要在Qt5中才可用。
Unicode字符串
我们尚不支持新的字符串类型。但是稍后可能会支持。
来试试吧!
如果你正在使用MSVC 2010,那么没什么可做的,你已经可以使用一些特性比如lambda和右值引用。
如果你正在使用gcc,你需要传递选项-std=c++0x
。
你可以在qmake的工程文件中指定:
QMAKE_CXXFLAGS += -std=c++0x
如果要用C++0x编译Qt,你可以
CXXFLAGS="-std=c++0x" ./configure
使用C++0x与使用老C++编译的Qt是二进制兼容的。而且要使用C++0x来编写自己的应用程序并不需要先用C++0x来编译Qt。