私は C++ テンプレートを少し悪用しており、何かを理解するのに苦労しています。基本型から継承する必要がある 2 つの型があるとしますが、速度上の理由から、仮想関数のオーバーヘッドを許容する余裕はありません (ベンチマークを行ったところ、仮想呼び出しによって問題が台無しになりました!)。
まず、ここに私が持っている2つのクラスがあります
template<class DataType> class Class1
{
//Lots of stuff here
}
template<Class DataType> class Class2
{
//The same stuff as in Class1, but implemented differently
}
典型的なooデザインでは、Class1
からClass2
継承しIInterface
、次のような関数を持つことができます
DoStuff(IInterface& MyInterface)
{
}
でもそれができないからこうしてしまった
template <class C>
DoStuff(C& c)
{
}
Class1
それを強制して同じインターフェイスを実装するものは (コンパイラ レベルで) 何もないので、きれいではないことはわかっていClass2
ますが、速度上の理由から、いくつかのルールを破っています。
私がやりたいのは、でコールバック関数を作成することですがDoStuff
、テンプレートで機能させる方法がわかりません(特にそこに隠しがあるため.
たとえば、これは現在機能します
DoStuff(char* filename)
{
switch (//figure out the type i need to make)
{
case 1: return DoStuff(Class1<int>(filename));
case 2: return DoStuff(Class1<double>(filename));
}
}
template<class DataType>
DoStuff(DataType* pdata)
{
return DoStuff(Class2<DataType>(pdata));
}
template<class C>
DoStuff(C c)
{
c.Print();
}
Class1
なぜand を使用するのClass2
か、あなたが尋ねていることがわかりました。ファイルの処理とメモリの処理の根本的な違いは非常に大きいため、(単にコンストラクターをオーバーロードして、異なる入力に対して異なる動作をさせるのではなく)、異なる種類の入力に対して異なるクラスを使用することは理にかなっています。繰り返しますが、これをベンチマークしましたが、すべての関数にcase
s/ sを含めるよりも、独自のクラスで特殊なケースを処理する方がはるかに高速です。if
したがって、私がやりたいのは、この実装の多くを後輩の開発者から隠してDoStuff
、異なる入力を処理するために 3 つの異なるオーバーロードされた s を作成する必要がないようにしたいということです。理想的には、何らかのタイプのコールバックを設定するだけで、#defines
呼び出されるクラスを作成しDoStuff
てオペレーターをオーバーロードし()
、ファンクターに作業させるだけで済みます。
私が抱えている問題DoStuff
は、作業を行う関数はテンプレート化されているだけです<class C>
が、C自体はテンプレート化されて<class DataType>
おり、すべてを一般的な方法で渡す方法がわかりません。たとえば、template <class C<DataType>>
またはは使用できませんtemplate<template< class DataType> class C>
。コンパイルされないだけです。
このネストされたテンプレート化されたクラスを使用して、関数またはファンクター (私は気にしません) のいずれかの一般的なコールバックを行うための良いトリックを誰かが持っていますか? 基本的に、データを格納しているクラスを気にしないジェネリック関数を記述し、使用するクラスを特定する最も一般的な関数によって呼び出されるものが必要です。
BigSwitch(CallBack,Inputs)
{
switch(//something)
{
case 1: return CallBack(Class1<Type>(Inputs))
case 2: return CallBack(Class2<Type>(Inputs))
}
}
このようにして、私が 1 つのBigSwitch
関数を作成し、他の人に CallBack 関数を作成してもらうことができます。
何か案は?
Jalfの明確化のための編集:
2 つの非常によく似たクラスがClass1
あり、Class2
基本的に同じタイプのデータを表しますが、データ ストアは大きく異なります。より具体的にするために、単純な例を使用します。Class1
は単純な配列でありClass2
、配列のように見えますが、メモリに格納するのではなく、ファイルに格納します (大きすぎてメモリに収まらないため)。だから私は彼らMemArray
にFileArray
今すぐ電話します。では、配列の合計が必要だとしましょう。私はこのようなことができます
template <class ArrayType, class ReturnType>
ReturnType Sum(ArrayType A)
{
ReturnType S=0;
for (int i=A.begin();i<A.end();++i)
{
S+=A[i];
}
return S;
}
しかし今、実際のデータを配列にロードする方法が必要です。メモリベースの配列の場合は、これを行います
MemArray<DataType> M(pData);
ファイルベースの場合は、これを行います
FileArray<DataType> F(filename);
これらの呼び出しは両方とも有効です (コンパイラはコンパイル時に両方のコード パスを生成するため)。
double MS=Sum<MemArray<DataType>,double>(M);
double FS=Sum<FileArray<DataType>,double>(F);
これはすべて、DataType が何であるかを知っていることを前提としていますが、ファイル ベースの配列の場合、ファイルを開いてヘッダーをクエリして配列内のデータの種類を知るまで、データ型がわからない場合があります。
double GetSum(char* filename)
{
int DataTypeCode=GetDataTypeCode(filename);
switch (DataTypeCode)
{
case 1: return Sum<FileArray<int>,double>(FileArray<int>(filename));
case 2: return Sum<FileArray<double>,double>(FileArray<double>(filename));
}
}
template <class DataType>
double GetSum(DataType* pData)
{
return Sum<MemArray<DataType>,double>(MemArray<DataType>(pData));
}
これはすべて機能しますが、オーバーロードされた 2 つGetX
の関数と、X
実行したいすべての関数を記述する必要があります。関数は、呼び出すGetX
を除いて、基本的に毎回同じコードX
です。だから私は次のようなものを書くことができるのが大好きです
double GetX(CallBackType X, char* filename)
{
int DataTypeCode=GetDataTypeCode(filename);
switch (DataTypeCode)
{
case 1: return X<FileArray<int>,double>(FileArray<int>(filename));
case 2: return X<FileArray<double>,double>(FileArray<double>(filename));
}
}
template <class DataType>
double GetX(CallBackType, DataType* pData)
{
return X<MemArray<DataType>,double>(MemArray<DataType>(pData));
}
私が電話できるように
GetX(Sum,filename)
その後、他の誰かが新しい関数を追加したい場合は、関数を記述して呼び出すだけです。
GetX(NewFunction,filename)
実際のアルゴリズムから入力/ストレージを抽象化できるように、オーバーロードされたGetX
関数と関数を記述する方法を探しています。X
通常、これは難しい問題ではありません。X
関数自体にテンプレート化されたテンプレート引数が含まれているため、問題が発生しているだけです。そこtemplate<class ArrayType>
には暗黙のArrayType<DataType>
隠し要素もあります。コンパイラはそれについて不満です。