Back to Blog home

C++ 11在Qt 5中的应用

Published on 星期一 七月 09, 2012 by Cheng Liang in Qt Qt 5 qtearth-blogs-chinese | Comments

参考原文:woboq: Olivier Goffart - C++11 in Qt5

C++ 11现在已经是C++标准,也就没有理由不在新的应用中使用。

Qt 4.8是第一个支持C++ 11特性的Qt版本。我在4.8发布之前写过一篇博客——Qt中的C++0x,这里就不再重复了。

显而易见的是,比起Qt 4.8,Qt 5利用了更多的C++ 11新特性。下面我们来一个个见识一下:

槽中使用Lambda表达式

Lambda表达式是C++ 11带来的最激动人心的特性之一。简而言之,它允许创建匿名函数。匿名函数则允许我们直接将一个函数作为参数传递,无需显式地声明。

在此之前,使用利用operator()在结构体中实现仿函数需要很多技巧性代码。

Qt 4.8实际已经可以使用这个特性。只不过在Qt 4.8中,Lambda表达式只能用在QtConcurrent的某些函数。现在,前面我们也介绍过,Qt 5有Qt 5中新的信号/槽语法,Lambda表达式有了更大的用武之地。回忆一下,在你需要编写槽代码的时候,即使只有一条语句,你也必须为它单独建立一个函数。这不是很麻烦吗?现在,我们有了更好的写法:

connect(sender, &Sender::valueChanged, [=](const QString &newValue) {
receiver->updateValue("senderValue", newValue);
});

Lambda表达式现在已经被MSVC 2010、GCC 4.5和clang 3.1实现。

Unicode字符串常量

C++ 11允许你使用u"HelloWorld"的形式生成UTF-16字符串。Qt利用这个特性增加了一个新的类QStringLiteral。这个类能够在编译时初始化QString,没有了运行时的时间消耗。参考之前关于QStringLiteral的文章

QString str = QStringLiteral("HelloWorld");

常量表达式 constexpr

C++ 11增加了新的关键字constexpr,指示某些内联函数可以在编译期运算。在Qt 5中,我们引入了Q_DECL_CONSTEXPR宏,当所使用的编译期支持constexpr时,这个宏可以生成constexpr,否则的话则是空白。

在Qt源代码中,我们也利用这个宏改写了许多函数,例如:

enum SomeEnum { Value1, Value2, Value3 };

Q_DECLARE_OPERATORS_FOR_FLAGS(QFlags<SomeEnum>)
// 上面一句声明了下述函数:
// Q_DECL_CONSTEXPR QFlags<SomeValue> operator|(SomeValue,SomeValue) {...}

int someFunction(QFlags<SomeEnum> value) {
switch (value) {
case SomeEnum::Value1:
return 1;
case SomeEnum::Value2:
return 2;
case SomeEnum::Value1 | SomeEnum::Value3:
// 这一个 case 仅在 C++ 11 中通过编译
// 因为 QFlags 运算符是 constexpr 的,也就是在编译期即可确定
// 而在之前版本则必须是
// QFlags<SomeValue> operator|(SomeValue,SomeValue)
// 这会引发一个错误,因为 case 语句要求编译期常量
return 3;
default:
return 0;
}
}

注意,这里我们在枚举值前面使用可SomeEnum::前缀,这是C++ 11允许的,但是之前版本的C++则不允许。

static_assert

在编译期使用static_assert检测问题,可以让C++ 11帮助我们可以组织处更好的错误信息。Qt 5引入了Q_STATIC_ASSERTQ_STATIC_ASSERT_X两个宏。当static_assert可用时,这两个宏将使用static_assert,否则使用一些模板技巧。

为了产生更好的编译错误信息,Qt在API不方便的地方大量使用了宏。

override和final

你遇到过这样的错误吗?自己定义的函数名看上去同父类的某个函数同名,但却的确有某些字母打错了,以至于并没有覆盖父类函数,从而让程序不能正确运行(或者是忘记了那函数名最后面的该死的const)?

现在,你可以选择在的确需要覆盖父类虚函数的地方加上Q_DECL_OVERRIDE。如果编译器支持的话,这个宏将展开为新增加的“override”关键字。这样的话,如果编译器支持C++ 11,那么如果是简单的字母错误,你就会得到一个错误;当你重构虚函数、却忘记修改子类时,同样会引发一个错误。

class MyModel : public QStringListModel {
//...
protected:
Qt::ItemFlags flags (const QModelIndex & index) Q_DECL_OVERRIDE;
};

注意,上面的flags()函数实际是想覆盖父类的同名函数,但是我们忘记了一个const,就会出现类似下面的错误:

mymodel.h:15: error: `Qt::ItemFlags MyModel::flags(const QModelIndex&)`
marked override, but does not override

如果虚函数不能覆盖,Qt也提供了另外一个宏,Q_DECL_FINAL,这个宏展开为final

deleted成员

当编译器支持deleted函数时,新增加的宏Q_DECL_DELETE将展开为=delete。这就允许我们能够为一些常见错误提供更好的编译器错误信息。

deleted函数用于显式地删除那些不允许编译器自动生成的函数(例如默认构造函数、默认拷贝运算符等)。deleted函数不能被调用,如果被使用的话,将会出现一个编译器错误。

我们可以将其用于Q_DISABLE_COPY宏。在此之前,为了实现同样的目的,我们的做法是将其声明为私有的。尽管效果相同,但是错误信息却并不友好。

右值引用和移动构造函数

在这里,我假定你明白什么叫做“右值引用”。如果不明白,我们会在后面的文章中详细说明。Qt 5已经在内部进行了调整,以便支持移动构造函数。因此,你可以大胆的使用它们了!

结论

对于C++ 11,MSVC不需要任何特殊的编译参数,而GCC和Clang则需要添加-std=c++0x

默认情况下,Qt 5本身会使用C++ 11编译参数进行编译(如果可能的话)。

如果你使用的是qmake,那么,在使用Qt 5构建的程序的.pro文件中,你需要增加这么一句:

CONFIG += c++11

(顺便提一句,在Qt 4中,如果你需要使用C++ 11的新特性,则应该增加gcc:CXXFLAGS += -std=c++0x。具体细节,我们会在后面的文章中说明。)

现在,好好利用C++ 11所带来的新特性吧!私以为,仅仅为了auto这一特性,就应该尽快使用C++ 11了!

Subscribe to Our Blog

Stay up to date with the latest marketing, sales and service tips and news.