Qt の C++0x 対応

この記事は Qt Blog の "C++0x in Qt" を翻訳したものです。
執筆: Olivier Goffart 2011年5月26日

QML と JavaScript テクノロジーに注目が集まっていますが、我々の幾人かはまだ C++ のコードの世界にとどまっています ;-) 。C++ は C++11 (C++0x として知られていたもの)にアップグレードしようとしています。最終提案が C++ 標準化委員会によって四月に承認され、この夏には正式な規格書が発行される見込みです。C++11 についてご存じなければ、WikipediaC++0x FAQ 等の関連するページを読むことをお勧めします。

C++0x の目標の一つは、この言語をより簡単にすることです。機能を追加して学習内容を増やすだけではその達成は困難です。しかし、C++ の「ほとんどの場合で使用される」サブセットをより簡単に、より直感的にすることは期待できます。

C++0x を使用するために待つ必要がないというのは良い知らせです。すぐにでも使い始められます。あなたが使っているコンパイラは(GCC か MSVC 2010 であれば)、おそらく既に C++0x のいくつかの機能をサポートしています。

C++0x を Qt 4.7 といったリリース済みのバージョンの Qt で使うことも出来ますが、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

リストの初期化

Qt 4.8 では QVector や QList、QStringList に中括弧を利用した初期化を可能にする新たなコンストラクタを追加しました。

例えば以下のように、

QVector<int> testsData { 1, 2, 10, 42, 50, 123  };
QStringList options = { QLatin1String("foo"), QLatin1String("bar") };

各要素でコンテナを初期化することができます。

右辺値参照と Move semantics

ほとんどの Qt のクラスは [qt "暗黙の共有" l=implicit-sharing] を行います。これらのクラスでは(コピーオンライトのため)データを変更しない場合のコピーが効率的に行えます。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 の古いデータはテンポラリ vector が破棄されるときに削除され、意味のないコピーが発生しません。これこそが C++0x の Move semantics です。また、それは 右辺値参照 によって実現されます。

暗黙に共有されたクラスのコピーコストが低かったとしても、0にはなりません。リファレンスカウントの増減は必要ですし、クラスの private なデータにアクセスする必要があるため operator= を inline 関数にして呼び出すこともできません(バイナリコンパチビリティを確保するためには inline 関数にすることができません)。

それでは、Qt 4.8 の QImage の operator= を見てみましょう

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

この関数は二つの QImage の内部データを交換しているだけなので、呼ばれたときにこれまでの動作に比べると非常に低いコストで実行できます。Qt のほとんどの暗黙の共有クラスにこのような変更を加えました。

全ての Qt のコンテナクラスは operator= でたくさんのテンポラリ変数を使っているため、Qt のスピードが若干改善されるかもしれません。それは C++0x をサポートした Qt をビルドする理由になるかもしれません(下記を参照ください)。

範囲ベースの for ループ

Qt には既に非常に便利な foreach があります。Boost のような他のライブラリにも同様のものがあります。
Qt の foreach は複雑なマクロで動いています。それに対して、C++0x ではより進んで言語の構成要素として作られています。
そのため、以下のようなコードではなく、

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

このようなコードを書くことができます。

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

この二つにはわずかな違いがあります。Qt の foreach では繰り返しの前にコンテナがコピーされます。
これは暗黙の共有を行っている Qt のコンテナクラスにとっては軽い処理ですが、その中身全体をコピーする標準のコンテナでは重たい処理になります。
C++0x の範囲ベースの for はコピーを行いません。そのため、繰り返しの際にコンテナからアイテムを削除したときの動作は未定義です。
新たな for では Qt のコンテナが共有されており、const では無い場合には begin()end() 関数を呼び出してそのコピーを作成しようとするでしょう。そのため、const なコンテナを通した方がパフォーマンスが良くなります。

template<class T> const T &const_(const T &t) { return t; }
for(auto it : const_(vector)) { ... }

ラムダ関数

QtConcurrent のいくつかの関数でラムダ関数を試してみました。
試したのは QtConcurrent::runQtConcurrent::map です。
しかし、QtConcurrent::mapped では上手く動きませんでした。今後修正されるでしょう。

[qt "Qtconcurrent::map のドキュメント" l=qtconcurrentmap m=#concurrent-map v=4.7] の scale サンプルは下記のように書き換えることができます。

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

シグナル・スロットの接続におけるラムダ関数の利用についても研究していますが、それは Qt 5 での課題になるでしょう。

ユニコード文字列

新しい文字列リテラルはまだサポートしていませんが、今後実装されるでしょう。

試しましょう!

MSVC 2010 を既に使っているのであれば、特別なことは要りません。ラムダ関数や右辺値参照などのいくつかの機能を既に使うことができます。

gcc を使っている場合は、その引数に -std=c++0x を付ける必要があります。
qmake のプロジェクトファイルに下記のような行を追加すればオプションを追加します。

QMAKE_CXXFLAGS += -std=c++0x

Qt 自体を C++0x でコンパイルしたければ、下記のようにします。

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

C++0x でコンパイルされた Qt は古き良き C++ でコンパイルされたものとバイナリコンパチビリティを保ちます。また、C++0x を用いたアプリケーションのコンパイルには Qt 自身を C++0x でコンパイルする必要はありません。


Blog Topics:

Comments