QtはなぜシグナルとスロットにMocを使うのか?

テンプレートはC++に組み込まれたメカニズムで、渡された引数の型に応じてコンパイラがその場でコードを生成することができます。そのため、テンプレートはフレームワーク作成者にとって非常に興味深いものであり、Qtでも多くの場所で高度なテンプレートを使用しています。しかし、限界もあります:テンプレートで簡単に表現できるものと、テンプレートでは表現できないものがあります。一般的なベクター・コンテナ・クラスは、ポインター型の部分的な特殊化でも簡単に表現できますが、文字列として与えられたXML記述に基づいてグラフィカル・ユーザー・インターフェースをセットアップする関数は、テンプレートとして表現できません。そして、その中間にグレーゾーンがある。コード・サイズ、可読性、移植性、使いやすさ、拡張性、堅牢性、そして最終的にはデザインの美しさを犠牲にしても、テンプレートを使ってハックできるものがある。テンプレートもCプリプロセッサも、信じられないほど賢く、気の遠くなるようなことをするために拡張することができる。しかし、そういったことができるからといって、必ずしもそれをすることが正しい設計の選択であるとは限らない。コードというものは、残念ながら書籍として出版されるものではなく、実際のオペレーティング・システム上で実際のコンパイラを使ってコンパイルされるものなのです。

Qtがmocを使う理由は以下の通りです:

構文が重要

構文は単なる砂糖ではありません。アルゴリズムを表現するために使う構文は、コードの読みやすさや保守性に大きく影響します。Qtのシグナルとスロットに使われている構文は、実際に非常に成功しています。構文は直感的で、使いやすく、読みやすいです。Qtを学習している人たちは、この構文がシグナルとスロットのコンセプトを理解し、活用するのに役立っていることに気づきます。このため、プログラマーはデザインパターンについて考える必要すらなく、最初から正しい設計を行うことができます。

コードジェネレータは良い

Qtのmoc (Meta Object Compiler)は、コンパイルされた言語の機能を超えるクリーンな方法を提供します。これは、標準的なC++コンパイラでコンパイルできる追加のC++コードを生成することで実現します。moc はC++ソース・ファイルを読み込む。Q_OBJECT マクロを含むクラス宣言が 1 つ以上見つかると、それらのクラスのメタ・オブジェクト・コードを含む別の C++ ソース・ファイルを生成します。moc によって生成された C++ ソース・ファイルは、コンパイルしてクラスの実装とリンクする必要があります(または、クラスのソース・ファイルに#included することもできます)。通常、moc は手動で呼び出されるのではなく、ビルド・システムによって自動的に呼び出されるため、プログラマーによる追加の作業は必要ありません。

Qt が使用しているコードジェネレーターはmoc だけではありません。もう一つの顕著な例は、uic (User Interface Compiler)です。これはXMLで記述されたユーザーインターフェイスを受け取り、フォームをセットアップするC++コードを作成します。Qt以外でも、コードジェネレータは一般的です。例えば、rpcidl は、プログラムやオブジェクトがプロセスやマシンの境界を越えて通信できるようにします。また、lexyacc が最もよく知られているもので、膨大な種類のスキャナ・ジェネレータやパーサ・ジェネレータがあります。これらは文法仕様を入力とし、ステートマシンを実装するコードを生成する。コード・ジェネレーターに代わるものは、ハックされたコンパイラー、プロプライエタリな言語、あるいは一方通行のダイアログやウィザードを備えたグラフィカルなプログラミング・ツールであり、コンパイル時ではなく設計時に不明瞭なコードを生成する。私たちは、お客様をプロプライエタリなC++コンパイラや特定の統合開発環境に固定するのではなく、お客様がお好きなツールを使用できるようにします。生成されたコードをソース・リポジトリに追加することをプログラマーに強制するのではなく、当社のツールをビルド・システムに追加することを奨励しています。

GUIは動的

C++は標準化された強力で精巧な汎用言語です。オペレーティング・システム全体、データベース・サーバー、ハイエンドのグラフィックス・アプリケーションから一般的なデスクトップ・アプリケーションまで、あらゆる種類のアプリケーションにまたがり、これほど幅広いソフトウェア・プロジェクトで利用されている唯一の言語です。C++の成功の鍵のひとつは、ANSI Cとの互換性を維持しつつ、最大限のパフォーマンスと最小限のメモリ消費に焦点を当てたスケーラブルな言語設計にあります。

このような利点がある一方で、いくつかの欠点もある。C++の場合、静的オブジェクト・モデルは、コンポーネント・ベースのグラフィカル・ユーザー・インターフェイス・プログラミングに関しては、Objective Cの動的メッセージング・アプローチに比べて明らかに不利である。ハイエンドのデータベース・サーバーやオペレーティング・システムに適した設計が、GUIフロントエンドに適しているとは限りません。moc では、この欠点を利点に変え、安全で効率的なグラフィカル・ユーザー・インターフェイス・プログラミングの課題に対応するために必要な柔軟性を追加しました。

私たちのアプローチは、テンプレートでできることをはるかに超えています。例えば、オブジェクト・プロパティを持つことができる。また、オーバーロードされたシグナルやスロットを持つことができます。これは、オーバーロードが重要な概念である言語でプログラミングするときに自然に感じられます。つまり、バイナリの互換性を壊すことなく、新しいシグナルを追加できる。

もうひとつの利点は、オブジェクトのシグナルとスロットを実行時に探索できることだ。接続するオブジェクトの正確な型を知らなくても、型安全なコール・バイ・ネームを使って接続を確立できる。これはテンプレートベースのソリューションでは不可能だ。このような実行時のイントロスペクションは、例えばQt Widgets DesignerのXML UIファイルから生成され接続されるGUIなど、新しい可能性を開きます。

呼び出し性能がすべてではない

Qtのシグナルとスロットの実装は、テンプレートベースのソリューションほど高速ではありません。一般的なテンプレート実装では、シグナルを発信するのは通常の関数呼び出し4回分のコストですが、Qtでは関数呼び出し10回分に匹敵する労力が必要です。Qtのメカニズムには、汎用マーシャラー、イントロスペクション、異なるスレッド間のキュー呼び出し、そして最終的にはスクリプト性が含まれているため、これは驚くべきことではありません。過剰なインライン化やコード拡張に頼らず、比類のない実行時の安全性を提供する。Qtのイテレータは安全ですが、より高速なテンプレート・ベースのシステムでは安全ではありません。複数のレシーバーにシグナルを送る過程でも、プログラムがクラッシュすることなく、安全にレシーバーを削除することができます。この安全性がなければ、デバッグが困難なfree'dメモリの読み書きエラーでアプリケーションがクラッシュしてしまいます。

それにもかかわらず、テンプレートベースのソリューションは、シグナルとスロットを使用するアプリケーションのパフォーマンスを向上させることはできないのでしょうか?Qtがシグナルを通してスロットを呼び出すコストにわずかなオーバーヘッドを追加しているのは事実ですが、呼び出しのコストはスロットのコスト全体のわずかな割合でしかありません。Qtのシグナルとスロットのシステムに対するベンチマークは、通常空のスロットで行います。スロットの中で何か有用な処理、例えば簡単な文字列操作などを行うと、呼び出しのオーバーヘッドは無視できる程度になります。Qtのシステムはとても最適化されているので、newやdeleteの演算子を必要とするもの(例えば、文字列操作やテンプレートコンテナからの挿入/削除など)は、シグナルを発するよりもかなりコストがかかります。

余談:パフォーマンスが重要なタスクのタイトな内部ループにシグナルとスロットの接続があり、この接続がボトルネックになっている場合は、シグナルとスロットではなく、標準的なリスナー・インターフェース・パターンを使うことを考えてください。このような場合、おそらく1:1の接続しか必要としないでしょう。例えば、ネットワークからデータをダウンロードするオブジェクトがある場合、要求されたデータが到着したことを示すためにシグナルを使用するのは完全に賢明な設計です。しかし、コンシューマーに1バイトずつ送信する必要がある場合は、シグナルやスロットではなく、リスナー・インターフェースを使用します。

制限なし

シグナルとスロットのためのmoc 、テンプレートではできない他の便利な機能を追加することができた。その中には、生成されたtr() 関数を介したスコープ付きトランスレーションや、イントロスペクションと拡張ランタイム型情報を備えた高度なプロパティシステムがある。Qt Widgets Designerのような強力で汎用的なユーザー・インターフェース・デザイン・ツールは、強力で内省的なプロパティ・システムがなければ(不可能ではないにしても)書くのがかなり難しくなるでしょう。しかし、ここで終わりではありません。私たちは、システムのRTTIに依存せず、その制限を共有しない動的なqobject_cast<T>()メカニズムも提供しています。この機構は、動的にロードされたコンポーネントから安全にインターフェースを問い合わせるために使用される。もうひとつの応用領域は、動的メタオブジェクトである。例えば、ActiveXコンポーネントを取り込み、実行時にその周りにメタオブジェクトを作成することができます。また、メタオブジェクトをエクスポートすることで、QtコンポーネントをActiveXコンポーネントとしてエクスポートすることもできます。テンプレートでは、これらのことはできません。

moc 、C++は本質的に、C++独自のパフォーマンスとスケーラビリティの利点を維持しながら、Objective-CやJava Runtime Environmentの柔軟性を提供してくれます。これが、今日のQtを柔軟で快適なツールにしているのです。

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。