最近、C ++でのテンプレートメタプログラミングに関する記事を 1つ読みました。 そして、そのようなコメントがありました :「インターフェイス、実装、ファクトリー、定義、構成、およびあらゆるものに対して、まったく同じレベルのカスタマイズで同じことができます。」 一般的に、記事と議論のモラル-アレクサンドレスクからのこれらのテンプレートは、人生ではあまり必要ありません。
私は自分の仕事を思い出しました。直交設計のアイデアが彼(Alexandrescu)に大いに役立ちました。 あなたと共有したいです。
問題
私はコンピューターとマイクロコントローラーデバイス用のプログラムを書いています。 そして、私はしばしばそれらの間の接続を整理する必要があります。 そして、いつものように、プログラムではエラーをキャッチする必要があります。 また、PCプログラムのデバッグがそれほど難しくない場合、マイクロコントローラーでは事態はさらに悪化します(ある意味、あらゆる種類のインサーキットデバッガーがありますが、リアルタイムタスクには適していません)。 まあ、それはポイントでもありません-接続自体をデバッグする必要がある場合があります! つまり、情報は送られるように見えますが、どういうわけか間違っています...これは、プログラムの開発の開始時に重要です-接続自体、プロトコルなどがデバッグされるとき。どういうわけか見つけることができませんでした。 何かが私の目を引きましたが、それはすべてではありませんでした(またはそれは、しかし非常に支払われました)。
そして突然、私は考えました-なぜ私は自分でそのようなプログラムを書かないのですか? 実際、すべてのビジネス-読み取り/書き込み、オープン/クローズ機能では、外部ファイルへの書き込み機能を追加します。 そこで、#defineを使用して、これらの機能が不要な場合は無効にします。
これと並行して、ローカルネットワークとグローバルネットワーク上のプロセス間でメモリ内のデータを交換するための便利なクラスをいくつか作成しました。 また、この交換をデバッグする必要がありました。
そことそこの両方で、私は共通の論理プロトコルを使用しました-メッセージには禁止された文字があり、これはパケットの開始/終了のサインです。
そして今、アレクサンドレスクのこの本は私の目を引きます。 何かがここで何かをかき立てることができるという私の頭の中で鐘が鳴りました...
実際、この同じ論理プロトコルを実装する単一の基本クラスを作成したかったのです。 このクラスは、交換の物理的性質(USB、プロセス間交換(Windowsで、メモリマッピングが使用された)、ローカルエリアネットワーク交換(名前付きパイプ)、グローバル交換(ソケット))も指定する必要があります。 デバッグ機能も作りたかった。 私もしたかった...
問題の声明
そのため、次のプロパティ(または「ポリシー」)で設定される基本クラスを作成する必要があります。
- 物理的性質 -使用される通信方法。 今、私はFTDIからUSB共有、メモリへのマッピング、名前付きパイプ、ソケットを実装しました。 ここでCOMポートに交換を追加したいと思います。
- 処理方法 -最初は「入力はありますか?」、「出力はありますか?」、「通信の状態は?」という機能だけでした。 次に、集中的な非同期交換用のプログラムを作成したときに、マルチタスク交換アルゴリズムを実装しました。 したがって、呼び出しメソッドには2つの異なるポリシーがあります-機能的およびマルチスレッドです。
- 交換保護 -私のプログラムがマルチタスク環境で動作し、異なるスレッドが交換デバイスに同時にアクセスできることを忘れないでください。 またはそうでないかもしれません。 したがって、別の「プレーン」を取得します-シングルタスク(単純な場合)、クリティカルセクションによる保護。 必要に応じて、ミューテックスで保護しますが、これまでのところ必要はありませんでした。
- ファイルに報告する -必要かどうか 必要に応じて-どのような形で? 区切りテキストファイルを書き込むオプションを作成しました。 Excelやその他の同様のプログラムに簡単にエクスポートできます。
- 時間測定 -時々、パケット間の時間間隔を知ることが重要です。 それらはさまざまな方法で測定できます(タスクに応じて)。 大まかな時間測定(GetTicksCount)、正確(Windowsでの高精度タイマー)、超精密( RDTSCタイマー)を行いました。
そして最も重要なことは、最初の2つのポリシーのみが重要です-通信の性質と通信イベントの処理方法です。 その他はすべて無視できます。 したがって、「空の」ポリシーがあります-何もしません。
はい、もう1つ-このクラスは普遍的なソリューションであると主張していません。 異なるメソッドやクラスを動的に接続することについては話していません。 すべてのグローバル設定は、プログラムの1か所に実装する必要があります-そして、作業中ずっと変わらないようにします。
どうした
その結果、InterConnectテンプレートクラスが作成されました。
template < typename Phys, // physical layer of connection - shared memory, USB, RS-232, LAN... typename Call, // call mode - function style, thread with events typename Prot = NoStrategy, // protection layer - protection based on critical sections, multiprocess protection etc. typename Log = NoStrategy, // logging mode - no logging (default), to file, to external process typename Timer = NoStrategy // timer mode - no timer (default), based on GetTickCount, high frequency timer, RDTSC > class InterConnect { protected: Phys _phys; Call _call; Prot _prot; Log _log; Timer _timer; // ... };
クラスの本文では、このポリシーが使用されているかどうかを常に確認しています。 たとえば、バイト書き込み機能の実装方法は次のとおりです。
UPD-例外があり、削除されました
template <typename Phys, typename Call, typename Prot, typename Log, typename Timer> bool InterConnectStorage<Phys,Call,Prot,Log,Timer>::send_byte (const unsigned char Val) { byte val; bool res = true; Prot::ProtectByte pr (_prot); // ... res &= _call.send (_phys, Val); // ... if (Log::to_use ()) { if (Timer::to_use ()) { _timer.stamp (); _log.send (Val, _timer.dprev (), _timer.dstart (), res); } else _log.send (Val, 0, 0, res); return res; } }
ここではすべてが明らかなようです。 「保護」ポリシーから、バイト保護機能を呼び出します(他の場所では、パケット全体が保護されます)。 「交換組織」ポリシーから呼び出し関数を呼び出します(オブジェクト「物理的性質」、実際にはバイトを渡します)。 次に、「レポートレコード」ポリシーからチェックを呼び出します。このポリシーはありますか? その場合、情報をレポートに書き込みます。 「カウントダウン」ポリシーが使用される場合、時間に基づいて書き込みます(「イベント」マークをスタンプ機能で配置し、レポートに書き込みます)、いいえ-単にレポートに書き込みます。
どこにでもチェックがあります-このポリシーは使用されていますか? 静的関数bool to_use(void)はこの質問に答えます。 静的で非常にシンプルなため、コンパイラはコードを最適化できます-まったく作成しないか(ポリシーを使用しない場合)、チェックせずにすぐに呼び出します。
その結果、非常に面倒なC ++コードがアセンブラーで非常にシンプルで最適なものに変わります。
使用する
その結果、非常に少ないコードで構成された非常に便利なツールが手に入りました。
小さなUSB共有プログラムの例を次に示します。
typedef InterConnectStrat::FTDIAccess Phys; // typedef InterConnectStrat::ThreadCall <Phys> Call; // typedef InterConnectStrat::CritSectProtectPack Prot; // typedef InterConnectStrat::NoStrategy Timer; // #if defined (MakeLogFile) typedef InterConnectStrat::TabulatedASCIILogFile Log; // File _log; // #else typedef InterConnectStrat::NoStrategy Log; // #endif InterConnectStorage <Phys, Call, Prot, Log, Timer> _con; //
MakeLogFile定数を使用して、プログラムのロジックを完全に変更します。 もちろん、再コンパイルには時間がかかりますが、結果のコードは非常に効果的です-速度とリソースの両方、および機能の両方の面で。
このコードでは、パッケージ間の間隔は重要ではありませんが、このプログラムの開発の開始時には必要でした。 typedef / * ... * /タイマーのみを変更しました-そして、すべてが設定されました。
途中で、メッセージに対するプログラムの反応を調べました。 これを行うために、交換の性質をUSBからプロセス間に変更しました。 必要なパッケージを送信する2つ目のコンソールシンプルなアプリケーションを作成しました。 そして、メインプログラムの反応を正しいものに調整しました。 それから、再びtypedef Physを変更し、プログラムの1か所(最大5行)で通信パラメーターを設定しました-そしてすべてがUSBで動作し始めました。 非常にシンプルで便利です!
また、パケットの開始/終了(必要に応じて、マルチタスク環境で保護モードを設定します)、バイトの送信など、主な操作をオーバーロードしました。その結果、パケットは次のように記述されます。
// . _con++; _con <<= SetProgramDate; _con <<= _HEX_date.get_Unix_time (); --_con; // . _con++; _con <<= GotoProgram; --_con;
コードはそれ自体を物語っています。
ソリューションの美しさをお楽しみください! 初期化ポイントを除いて、プログラム全体で、私はプログラム全体が何であれ、プログラムがマルチタスクであるかどうか、レポートにエントリがあるかどうか、どのような交換プロトコルかには無関心です。 リストは非常に簡単です!
ただし、すべての種類のクラスファクトリなどとは異なり、結果のコードは最適です。不要なチェックや遷移はありません。
他の方法の比較
そして今、楽しみのために、C ++ 9xを使用して同じ問題を解決する他の方法を検討してください。 私が見逃したことをコメントで教えてください:
- 継承 -テンプレートとは異なり、必要な機能(テンプレートにはない)の存在を保証します。 しかし、他のすべて-動的な接続、さまざまな仮想機能-はここでは必要ありません。 さらに、仮想関数は組み込み関数では最適化できません。 テンプレートには間違いなくボーナスがあります。
- 工場 -私はまだそれらに遭遇していません。 私が理解しているように、これは以前の方法をさらに発展させたものです。 最適化とテンプレートよりもパフォーマンスの点で。
- typedef-おそらく、これはすべてテンプレートなしで実装できます。 ただし、テンプレートの.hファイルは、クラスが使用されていなくてもコンパイルできます。そのような数は、typedefでは機能しません。 はい、そしてtypedefに関してはどういうわけか非常に難しいでしょう:-);
- #define -ahem ...コメントはないと思います;-)
一般に、このツールは非常に便利で高品質であることが判明しました。 完全に異なるプレーンで機能を開発できます。
PS Alexandrescu-よくやった、何と言っても!