私の職場では、 iostream、string、vector、map、および奇数アルゴリズムまたは 2 つを使用する傾向があります。テンプレート手法が問題の最善の解決策である状況は、実際にはあまり多くありません。
ここで私が探しているのは、アイデアと、必要に応じて、実際に遭遇した問題に対する新しいソリューションを作成するためにテンプレート手法をどのように使用したかを示すサンプル コードです。
賄賂として、あなたの回答に対する賛成票を期待してください。
私の職場では、 iostream、string、vector、map、および奇数アルゴリズムまたは 2 つを使用する傾向があります。テンプレート手法が問題の最善の解決策である状況は、実際にはあまり多くありません。
ここで私が探しているのは、アイデアと、必要に応じて、実際に遭遇した問題に対する新しいソリューションを作成するためにテンプレート手法をどのように使用したかを示すサンプル コードです。
賄賂として、あなたの回答に対する賛成票を期待してください。
テンプレートに関する一般情報:
テンプレートは、同じコードを使用する必要があるが、コンパイル時に型がわかっている異なるデータ型で動作する必要がある場合に役立ちます。また、あらゆる種類のコンテナ オブジェクトがある場合も同様です。
非常に一般的な使用法は、ほぼすべてのタイプのデータ構造です。例: 片方向リスト、両方向リスト、ツリー、トライ、ハッシュテーブルなど
もう 1 つの非常に一般的な使用法は、アルゴリズムの並べ替えです。
テンプレートを使用する主な利点の 1 つは、コードの重複を削除できることです。コードの重複は、プログラミング時に避けるべき最大の事柄の 1 つです。
関数 Max をマクロまたはテンプレートの両方として実装することもできますが、テンプレートの実装はタイプ セーフであるため、より優れています。
そして今、クールなものに:
テンプレート メタプログラミングも参照してください。これは、実行時ではなくコンパイル時にコードを事前評価する方法です。テンプレートのメタプログラミングには不変の変数しかないため、その変数は変更できません。このテンプレートのおかげで、メタプログラミングは関数型プログラミングの一種と見なすことができます。
ウィキペディアのテンプレート メタプログラミングの例を確認してください。テンプレートを使用してコンパイル時にコードを実行する方法を示します。したがって、実行時には、事前に計算された定数があります。
template <int N>
struct Factorial
{
enum { value = N * Factorial<N - 1>::value };
};
template <>
struct Factorial<0>
{
enum { value = 1 };
};
// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
int x = Factorial<4>::value; // == 24
int y = Factorial<0>::value; // == 1
}
私は主に Boost と STL で多くのテンプレート コードを使用してきましたが、作成する必要はほとんどありませんでした。
数年前の例外の 1 つは、Windows PE 形式の EXE ファイルを操作するプログラムにありました。同社は 64 ビットのサポートを追加したいと考えていましたがExeFile
、ファイルを処理するために私が作成したクラスは 32 ビットのファイルでしか機能しませんでした。64 ビット バージョンを操作するために必要なコードは基本的に同じでしたが、別のアドレス タイプ (32 ビットではなく 64 ビット) を使用する必要があり、他の 2 つのデータ構造も異なっていました。
std::string
と の両方をサポートする STL の単一のテンプレートの使用に基づいて、異なるデータ構造とアドレス タイプをパラメーターとして使用して、テンプレートをstd::wstring
作成してみることにしました。ExeFile
行を使用しなければならない箇所が 2 箇所ありましたが#ifdef WIN64
(処理要件が少し異なります)、それほど難しくはありませんでした。現在、そのプログラムでは 32 ビットと 64 ビットが完全にサポートされており、テンプレートを使用するということは、それ以降に行ったすべての変更が両方のバージョンに自動的に適用されることを意味します。
テンプレートを使用して独自のコードを作成する場所の 1 つは、Andrei Alexandrescu が Modern C++ Design で説明しているように、ポリシー クラスを実装することです。現在、BEA\h\h\h Oracle の Tuxedo TP モニターと対話する一連のクラスを含むプロジェクトに取り組んでいます。
Tuxedo が提供する機能の 1 つは、トランザクションの永続キューです。そのため、キューと対話するクラス TpQueue があります。
class TpQueue {
public:
void enqueue(...)
void dequeue(...)
...
}
ただし、キューはトランザクション対応であるため、必要なトランザクションの動作を決定する必要があります。これは TpQueue クラスの外で個別に行うことができますが、各 TpQueue インスタンスがトランザクションに関する独自のポリシーを持っている場合は、より明示的でエラーが発生しにくいと思います。したがって、次のような一連の TransactionPolicy クラスがあります。
class OwnTransaction {
public:
begin(...) // Suspend any open transaction and start a new one
commit(..) // Commit my transaction and resume any suspended one
abort(...)
}
class SharedTransaction {
public:
begin(...) // Join the currently active transaction or start a new one if there isn't one
...
}
TpQueue クラスは次のように書き直されます。
template <typename TXNPOLICY = SharedTransaction>
class TpQueue : public TXNPOLICY {
...
}
したがって、TpQueue 内では、必要に応じて begin()、abort()、commit() を呼び出すことができますが、インスタンスの宣言方法に基づいて動作を変更できます。
TpQueue<SharedTransaction> queue1 ;
TpQueue<OwnTransaction> queue2 ;
私は(Boost.Fusionの助けを借りて)テンプレートを使用して、開発中のハイパーグラフライブラリのタイプセーフな整数を実現しました。(ハイパー)エッジIDと頂点IDがあり、どちらも整数です。テンプレートを使用すると、頂点IDとハイパーエッジIDが異なるタイプになり、一方を使用すると、もう一方が予期されたときにコンパイル時エラーが発生しました。実行時のデバッグで発生する頭痛の種を大幅に軽減しました。
これは、実際のプロジェクトからの 1 つの例です。次のようなゲッター関数があります。
bool getValue(wxString key, wxString& value);
bool getValue(wxString key, int& value);
bool getValue(wxString key, double& value);
bool getValue(wxString key, bool& value);
bool getValue(wxString key, StorageGranularity& value);
bool getValue(wxString key, std::vector<wxString>& value);
そして、「デフォルト」値を持つバリアント。キーが存在する場合はその値を返し、存在しない場合はデフォルト値を返します。テンプレートのおかげで、6 つの新しい関数を自分で作成する必要がなくなりました。
template <typename T>
T get(wxString key, const T& defaultValue)
{
T temp;
if (getValue(key, temp))
return temp;
else
return defaultValue;
}
COM を使用し、別のインターフェイスを直接または [ IServiceProvider
]( http://msdn.microsoft.com/en-us/library/cc678965(VS.85).aspx)を介して実装できるオブジェクトへのポインターを受け入れます。このヘルパー キャストのような関数を作成します。
// Get interface either via QueryInterface of via QueryService
template <class IFace>
CComPtr<IFace> GetIFace(IUnknown* unk)
{
CComQIPtr<IFace> ret = unk; // Try QueryInterface
if (ret == NULL) { // Fallback to QueryService
if(CComQIPtr<IServiceProvider> ser = unk)
ser->QueryService(__uuidof(IFace), __uuidof(IFace), (void**)&ret);
}
return ret;
}
私が定期的に使用するテンプレートは、多数のコンテナー クラス、ブースト スマート ポインター、スコープガード、いくつかの STL アルゴリズムです。
私がテンプレートを書いたシナリオ:
異なるタイプのオーバーロードの一般的な実装。
bool ContainsNan(float * , int) bool ContainsNan(double *, int)
どちらも(ローカル、非表示の)ヘルパー関数を呼び出すだけです
template <typename T>
bool ContainsNanT<T>(T * values, int len) { ... actual code goes here } ;
型に特定のプロパティ (バイナリ シリアル化など) がある限り、型に依存しない特定のアルゴリズム。
template <typename T>
void BinStream::Serialize(T & value) { ... }
// to make a type serializable, you need to implement
void SerializeElement(BinStream & strean, Foo & element);
void DeserializeElement(BinStream & stream, Foo & element)
仮想関数とは異なり、テンプレートではより多くの最適化を行うことができます。
一般に、テンプレートを使用すると、多数の型に対して 1 つの概念またはアルゴリズムを実装でき、コンパイル時に既に相違点が解決されています。
テンプレートを使用して、関数オブジェクトの型を指定します。私は関数オブジェクト (統合する関数、最適化する関数など) を引数として取るコードをよく書きますが、継承よりもテンプレートの方が便利だと思います。したがって、関数オブジェクト (インテグレーターやオプティマイザーなど) を受け取るコードには、操作対象の関数オブジェクトの種類を指定するテンプレート パラメーターがあります。
私は個人的に、ある種のトップダウン設計とボトムアップ実装を強制する手段として、Curiously Recurring Template パターンを使用しました。例としては、フォームとインターフェイスの両方に関する特定の要件がコンパイル時に派生型に適用されるジェネリック ハンドラーの仕様があります。次のようになります。
template <class Derived>
struct handler_base : Derived {
void pre_call() {
// do any universal pre_call handling here
static_cast<Derived *>(this)->pre_call();
};
void post_call(typename Derived::result_type & result) {
static_cast<Derived *>(this)->post_call(result);
// do any universal post_call handling here
};
typename Derived::result_type
operator() (typename Derived::arg_pack const & args) {
pre_call();
typename Derived::result_type temp = static_cast<Derived *>(this)->eval(args);
post_call(temp);
return temp;
};
};
このようなものを使用して、ハンドラーがこのテンプレートから派生していることを確認し、トップダウンの設計を適用してから、ボトムアップのカスタマイズを可能にすることができます。
struct my_handler : handler_base<my_handler> {
typedef int result_type; // required to compile
typedef tuple<int, int> arg_pack; // required to compile
void pre_call(); // required to compile
void post_call(int &); // required to compile
int eval(arg_pack const &); // required to compile
};
これにより、handler_base<> 派生型のみを処理する汎用ポリモーフィック関数を使用できます。
template <class T, class Arg0, class Arg1>
typename T::result_type
invoke(handler_base<T> & handler, Arg0 const & arg0, Arg1 const & arg1) {
return handler(make_tuple(arg0, arg1));
};
私はかつて次のコードを見ました:
void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
// three lines of code
callFunctionGeneric1(c) ;
// three lines of code
}
10回繰り返します:
void doSomethingGeneric2(SomeClass * c, SomeClass & d)
void doSomethingGeneric3(SomeClass * c, SomeClass & d)
void doSomethingGeneric4(SomeClass * c, SomeClass & d)
// Etc
各関数には同じ 6 行のコードがコピー/貼り付けされ、そのたびに別の関数 callFunctionGenericX を同じ番号のサフィックスで呼び出します。
全体を完全にリファクタリングする方法はありませんでした。だから私はリファクタリングをローカルに保ちました。
このようにコードを変更しました(メモリから):
template<typename T>
void doSomethingGenericAnything(SomeClass * c, SomeClass & d, T t)
{
// three lines of code
t(c) ;
// three lines of code
}
既存のコードを次のように変更しました。
void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
doSomethingGenericAnything(c, d, callFunctionGeneric1) ;
}
void doSomethingGeneric2(SomeClass * c, SomeClass & d)
{
doSomethingGenericAnything(c, d, callFunctionGeneric2) ;
}
等。
これはテンプレートのことをいくらかハイジャックしていますが、最終的には、型定義された関数ポインターで遊んだり、マクロを使用したりするよりはましだと思います。
明らかな理由 (さまざまなデータ型を操作してコードの重複を防ぐなど) はさておき、ポリシー ベースの設計と呼ばれる非常に優れたパターンがあります。ポリシーと戦略について質問しました。
では、この機能の優れた点は何でしょう。他の人が使用するインターフェイスを作成していると考えてください。インターフェイスは独自のドメイン内のモジュールであるため、インターフェイスが使用されることがわかっています。しかし、人々がそれをどのように使用するかはまだわかりません。ポリシーベースの設計により、将来の再利用のためにコードが強化されます。特定の実装が依存するデータ型に依存しなくなります。コードは単に「丸呑み」されています。:-)
特性はそれ自体素晴らしいアイデアです。特定の動作、データ、および型データをモデルに添付できます。特性により、これら 3 つのフィールドすべてを完全にパラメータ化できます。そして何よりも、コードを再利用可能にする非常に優れた方法です。
テンプレートをポリシー クラスとして使用して何かを実行できることは既に述べました。私はこれをよく使います。
また、一般的な方法でデータにアクセスするために、プロパティ マップ (詳細については boost サイトを参照) を使用してそれらを使用します。これにより、データの取得方法を変更することなく、データの保存方法を変更する機会が得られます。