1

私は 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か、あなたが尋ねていることがわかりました。ファイルの処理とメモリの処理の根本的な違いは非常に大きいため、(単にコンストラクターをオーバーロードして、異なる入力に対して異なる動作をさせるのではなく)、異なる種類の入力に対して異なるクラスを使用することは理にかなっています。繰り返しますが、これをベンチマークしましたが、すべての関数にcases/ 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、配列のように見えますが、メモリに格納するのではなく、ファイルに格納します (大きすぎてメモリに収まらないため)。だから私は彼らMemArrayFileArray今すぐ電話します。では、配列の合計が必要だとしましょう。私はこのようなことができます

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>隠し要素もあります。コンパイラはそれについて不満です。

4

2 に答える 2

3

質問の最初の部分に焦点を当てます(継承を使用していない理由):

コンパイル時のポリモーフィズムを実行し、基本クラスを介して派生クラスのメンバーにアクセスできるようにする一般的な方法は、CRTPパターンを使用することです。

template <typename T>
class IInterface {
  void DoStuff() {
    void static_cast<T*>(this)->DoStuff()
  }
};

class Class1 : IInterface<Class1> {
  void DoStuff(){...}
}

それはあなたの問題を解決しますか?

編集: ところで、お役に立ててうれしいのですが、次回はもう少し質問を構成してみてください。

私はあなたが何を求めているのかまったく見当がつかなかったので、これはあなたの質問の最初の 3 行に基づいて、暗闇の中に突き刺しただけでした。;)

達成しようとしていることを実際に説明することはなく、機能しない回避策がどのように見えるかだけを説明します。それが私たちが本当に知る必要があることなので、問題を述べるところから始めましょう。次に、現在の回避策に関する詳細を提供できます。また、コードを投稿するときは、コンテキストを追加してください。DoStuff() はどこから呼び出されますか? また、ジュニア開発者がそれらを定義する必要があるのはなぜですか? (あなたはすでにそれをしましたよね?)

そもそも、ジュニア開発者がこのコードで何をしていると言うでしょうか?

また、特定のケース (1 と 2) を提供しているのに、switch ステートメント自体 (//something) を提供していないのは混乱を招きます。

回答者が簡単に回答できるように努めれば、次回はより多くの (そしてより良い、より速い) 回答を得ることができます。:)

于 2009-05-13T21:46:22.337 に答える
0

「一般化されたコールバック」に関するあなたの質問については、boost::functionを使用できますが、それは本質的にカバーの下で仮想関数を使用します (そうではないかもしれませんが、少なくとも同様の概念です)。したがって、探しているパフォーマンスの違いはありません。そこにあります(実際、boost::function はおそらくヒープ割り当てのために遅くなります)。

于 2009-05-13T21:36:12.137 に答える