C++ 11 在 Qt 5 中的应用

参考原文:http://woboq.com/blog/cpp11-in-qt5.html

C++ 11 现在已经是 C++ 标准,也就没有理由不在新的应用中使用。Qt 4.8 是第一个支持 C++ 11 特性的 Qt 版本,不过这里,我们首先介绍的是,Qt 5 中如何结合使用 C++ 11。至于 Qt 4.8,我们会在后续文章中进行阐述。

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

slot 中使用 Lambda 表达式

Lambda 表达式是 C++ 11 带来的最激动人心的特性之一(豆子已经在前面几篇文章中不止一次说这样的话了 ;-P)。简而言之,它允许创建匿名函数。匿名函数则允许我们直接将一个函数作为参数传递,无需显式地声明。

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

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,没有了运行时的时间消耗。

QString str = QStringLiteral("HelloWorld");

常量表达式 constexpr

C++ 11 增加了新的关键字constexpr,指示某些 inline 函数可以在编译期运算。在 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&lt;SomeValue&gt; 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 不方便的地方大量使用了宏。

overridefinal

你遇到过这样的错误吗?自己定义的函数名看上去同父类的某个函数同名,但却的确有某些字母打错了,以至于并没有覆盖父类函数,从而让程序不能正确运行(或者是忘记了那函数名最后面的该死的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宏。在此之前,为了实现同样的目的,我们的做法是将其声明为private的。尽管效果相同,但是错误信息却并不友好。

右值引用和移动构造函数

在这里,我假定你明白什么叫做“右值引用”。如果不明白,我们会在后面的文章中详细说明。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 了!;-p

One Response

  1. 精英王子 2012年6月17日

Leave a Reply