テンプレートはどこで便利ですか?
-
04-07-2019 - |
質問
職場では、 iostream 、 string 、 vector 、 map 、および奇数を使用する傾向がありますアルゴリズムまたは2。テンプレートテクニックが問題の最良の解決策であるような状況を実際に発見したことはありません。
ここで探しているのはアイデアです。オプションで、テンプレートテクニックを使用して、実際に発生した問題の新しいソリューションを作成する方法を示すサンプルコードもあります。
賄briとして、あなたの答えに賛成票を投じることを期待してください。
解決
主にBoostとSTLで多くのテンプレートコードを使用しましたが、ほとんど 書く必要はほとんどありませんでした。
例外の1つは、数年前、Windows PE形式のEXEファイルを操作するプログラムにありました。同社は64ビットのサポートを追加したかったのですが、ファイルを処理するために作成したExeFile
クラスは32ビットのクラスでしか機能しませんでした。 64ビットバージョンを操作するために必要なコードは本質的に同一でしたが、異なるアドレスタイプ(32ビットではなく64ビット)を使用する必要があったため、他の2つのデータ構造も異なります。
STLが単一のテンプレートを使用してstd::string
とstd::wstring
の両方をサポートすることに基づいて、異なるデータ構造とアドレスタイプをパラメーターとして使用して、#ifdef WIN64
をテンプレートとして作成することにしました。まだ<=>行を使用しなければならない場所が2つありました(処理要件は少し異なります)が、それは実際には難しくありませんでした。そのプログラムでは現在32ビットと64ビットの完全なサポートがあり、テンプレートを使用することで、それ以降に行ったすべての変更が両方のバージョンに自動的に適用されます。
他のヒント
テンプレートに関する一般情報:
テンプレートは、同じコードを使用する必要があるが、異なるデータ型を操作する必要がある場合に便利です。このデータ型はコンパイル時に既知です。また、あらゆる種類のコンテナオブジェクトがある場合も同様です。
非常に一般的な使用法は、ほぼすべてのタイプのデータ構造です。例:単一リンクリスト、二重リンクリスト、ツリー、試行、ハッシュテーブル、...
別の非常に一般的な使用法は、アルゴリズムのソートです。
テンプレートを使用する主な利点の1つは、コードの重複を削除できることです。コードの重複は、プログラミング時に避けるべき最大の要素の1つです。
マクロまたはテンプレートの両方として関数Maxを実装できますが、テンプレートの実装は型安全であるため、より優れています。
そして、クールなものへ:
テンプレートメタプログラミングも参照してください。これはコンパイル時にコードを事前評価する方法です実行時ではなく。テンプレートのメタプログラミングには不変の変数のみがあるため、その変数は変更できません。このため、テンプレートのメタプログラミングは一種の関数型プログラミングと見なすことができます。
Wikipediaのテンプレートメタプログラミングのこの例をご覧ください。テンプレートを使用してコンパイル時にコードを実行する方法を示します。したがって、実行時には、事前に計算された定数があります。
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
}
テンプレートを使用して独自のコードを作成する1つの場所は、Modern C ++ DesignのAndrei Alexandrescuで説明されているように、ポリシークラスを実装することです。現在、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;
}
通常使用するテンプレートは、多数のコンテナクラスであり、スマートポインターをブーストします。スコープガード、いくつかのSTLアルゴリズム。
テンプレートを作成したシナリオ:
- カスタムコンテナ
- メモリ管理、void *アロケーターの上に型安全性とCTor / DTor呼び出しを実装
-
さまざまなタイプのオーバーロードの一般的な実装、例:
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つの概念またはアルゴリズムを実装でき、コンパイル時にすでに相違が解決されています。
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;
}
テンプレートを使用して、関数オブジェクトタイプを指定します。私はよく、関数オブジェクトを引数として取るコード(統合する関数、最適化する関数など)を記述します。テンプレートは継承よりも便利です。したがって、インテグレーターやオプティマイザーなどの関数オブジェクトを受け取るコードには、操作対象の関数オブジェクトの種類を指定するテンプレートパラメーターがあります。
明らかな理由(さまざまなデータ型での操作によるコード重複の防止など)は別として、ポリシーベースの設計と呼ばれるこの本当にクールなパターンがあります。 ポリシーvs戦略について質問しました。
さて、この機能の気の利いた点は何ですか。他のユーザーが使用するためのインターフェースを作成していると考えてください。インターフェースは独自のドメインのモジュールであるため、インターフェースが使用されることを知っています。しかし、あなたはまだ人々がそれをどのように使用するつもりかを知りません。ポリシーベースの設計は、将来の再利用のためにコードを強化します。特定の実装が依存するデータ型に依存しなくなります。コードは<!> quot; <!> quot;に丸sみされています。 :-)
特性はそれ自体素晴らしいアイデアです。特定の動作、データ、およびタイプデータをモデルに添付できます。特性により、これら3つのフィールドすべてを完全にパラメーター化できます。そして、最高の方法は、コードを再利用可能にする非常に良い方法です。
次のコードを見たことがあります:
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) ;
}
その他
これはテンプレートのことをややハイジャックしていますが、最終的には、typedefされた関数ポインターを使用したり、マクロを使用したりするよりも良いと思います。
私は個人的に、何らかの形のトップダウン設計とボトムアップ実装を実施する手段として、Curiously Recurring Template Patternを使用しました。例としては、コンパイル時に派生型にフォームとインターフェイスの両方の特定の要件が適用される汎用ハンドラーの仕様があります。次のようになります:
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 <!> lt; <!> gt;のみを処理する汎用ポリモーフィック関数を使用できます。派生型:
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));
};
テンプレートをポリシークラスとして使用して、何かをすることができることは既に述べました。これをよく使います。
また、プロパティマップ(一般的な方法でデータにアクセスするには、この詳細については、ブーストサイトを参照してください)。これにより、データの取得方法を変更することなく、データの保存方法を変更できます。