1

私が書いているライブラリには、クラスがあります:

template<typename T>
class my_class {};

そして関数:

template<typename T>
void my_function(...) {};

私のコードのユーザーがmy_function<T>インスタンス化しようとすると、コードのどこかで呼び出されたことを保証したいmy_class<T>. my_classつまり、対応するテンプレートをインスタンス化せずにテンプレートをインスタンス化しようとすると、コンパイル エラーを生成したいと考えていますmy_function

例:

int main() {
    my_func<int>()      // cause instantiation of my_func<int>

    my_class<int> foo;  // okay, because my_func<int> exists
    my_class<char> bar; // compile error! my_func<char> does not exist
}

my_class<T>内部で使用する必要がないmy_func<T>ので、インスタンス化しても自動的にインスタンス化されないことに注意してくださいmy_func<T>

編集[0]:my_func<T>ユーザーがそれを呼び出して、ライブラリに処理方法を伝える情報を渡す必要があるため、自分自身を呼び出すことはできませんmy_class<T>。すなわち。a を使用する場合my_class<char>は、コード内の特定の場所で明示的に指定する必要があります。コンストラクターが呼び出されたmy_class<T>かどうかをテストmy_func<T>して、呼び出されていない場合は実行時エラーを生成することもできますが、使用されているかどうかはコンパイル時に認識できるため、コンパイル時にエラーを生成できるようにしたいと考えていますmy_func<T>

編集[1]:理想的とは言えない方法の 1 つMY_FUNCは、そうでなければ不完全なテンプレート クラスのテンプレート特殊化を作成するマクロを作成することです...

template<typename T>
class incomplete;

#define MY_FUNC(T) template<> incomplete<T> { static const bool x = my_func<T>(); };

template<typename T>
class my_class {
    template<size_t i>
    class empty {};

    typedef empty<sizeof(incomplete<T>)> break_everything;
}

現在、ユーザーはmy_class<T>ifMY_FUNC(T)がどこかで使用されている場合にのみ使用できます。ただし、マクロを使用せず、ユーザーがMY_FUNC関数ではなくグローバルスコープで使用することを強制しないソリューションを好みます。

編集[2]:回答ありがとうございます。my_func<T>ユーザーが間違って呼び出していないことをコンパイル時に保証することは不可能だと認識しています。しかし、そうなる可能性をかなり低くし、早い段階で実行時エラーを発生させることができます。

彼らは、たとえば、と呼ばれる関数を定義することになっていますinitialise_library。関数は次のようになります。

void initialise_library() {
    my_func<int>();
    my_func<char>();
    etc..
}

my_funcライブラリの初期化中に、が呼び出されていないことを確認します。次に、 を呼び出しますinitialise_librarymy_funcs次に、すべてが呼び出されていることを確認します。これだけのことができます。ユーザーが定義initialise_libraryしていない場合、リンカー エラーが発生します。my_func<T>プログラムのどこかで使用する aを呼び出していない場合my_class<T>、(理想的には) コンパイル エラーが発生します。定義initialise_library my_func<T>どこかで使用した、間違った場所で使用した場合、プログラムの起動時に実行時エラーが発生します。

my_class<T>基本的に、私は彼らがコードのどこかに a を追加したり、 を忘れたりするのを止めたいと思ってmy_func<T>()initialise_libraryます。これをランタイム チェックする唯一の方法は、 の構築中ですmy_class<T>my_class<T>彼らが深くてめったに使用されないコードのチャンクでのみ使用する場合、これはこのチェックを行うのに厄介なほど遅い時間になります.

4

3 に答える 3

1

コンパイル時に関数の呼び出しの存在を検出できたとしても、その呼び出しが実際に実行時に行われたこと、および他の呼び出しの前に行われたことを確認することはまったく不可能です。したがって、とにかく実行時チェックを使用する必要があります。

于 2013-05-09T05:58:35.597 に答える
1

これを行うのが非常に難しく、ほぼ確実に不可能な理由がいくつかあります。

まず、コンパイル時に認識できること、特定のコンパイラがコンパイル時に実際に認識できること、コンパイラが追跡する必要がある標準で必要な情報、および追跡できる情報には違いがあることを理解する必要があります。実際に抽出して使用するコンパイル時(およびその情報を使用できるコンテキスト)。あなたが求めていることは理論的には可能だと思います(たとえば、すべてが単一の翻訳単位で発生するといういくつかの仮定の下で)。しかし、そのような情報自体を知る必要があることは言うまでもなく、コンパイラがそのような情報を提供できることを標準が要求していないことはほぼ確実です。また、ほとんどのコンパイラが、ごく些細な場合を除いて、その情報を知ることができるとは思えません。

この理由は簡単です。あなたの質問は、関数 A がポイント B の前のどこかで呼び出されたかどうかをコンパイラに尋ねることに要約されます。ここで、関数 A は関数テンプレートであり、ポイント B はクラス テンプレートのインスタンス化と最初の作成のポイントです。これは、コンパイラがポイント B に到達したときに、ポイント B より前の実行ポイントに対応するすべてのコードを分析 (およびテンプレートをインスタンス化) している必要があることを意味します。つまり、コンパイラは実行パスをたどる必要があります。コンパイル時全体。コンパイラはこれを行いません。関数テンプレートは、クラステンプレートオブジェクトを作成する前にその関数が実行される場合でも、ポイント B に到達する前にコンパイラがコンパイルできない他の関数に簡単に埋もれてしまう可能性があります。そして、その逆も真である可能性があります。

次に、枝の問題があります。関数テンプレートをインスタンス化しても、何らかの条件ステートメントや例外、またはそのためのゴーツーが原因で、それが実行されることを意味するわけではありません。クラステンプレートのコンストラクターに到達するまでに関数が確実に実行されているかどうかを確実に伝えるには、コンパイラーによって行われる分析は非常に狂っていなければなりません。

次に、翻訳単位の問題があります。関数テンプレートのインスタンス化とクラス テンプレートのインスタンス化が同じ翻訳単位に表示されない場合はどうなりますか? 関数テンプレートがインスタンス化されたことを検出するメカニズムが機能していたとしても、この場合、実行順序が正しかったとしても機能しません。

簡単に言えば、実行時チェックを使用するか、コンストラクターから関数を呼び出す方法を見つけます (おそらく、実行時にデフォルトのパラメーターとデバッグ警告メッセージを使用して)。

編集:一般に、コンパイル時に、関数呼び出し/コンストラクター呼び出しの特定の順序が初期化のチェーンに結び付けられることを保証する方法。簡単なトリックは、別のクラスのメンバー関数を呼び出すことによってのみ (たとえば、そのクラスのグローバル/シングルトン オブジェクトで) クラス テンプレートを構築可能にし、必要なパラメーターを指定して関数テンプレートを呼び出すことによってのみそのオブジェクトを構築可能にすることです。多くの場合、「一度初期化する」変数として静的ローカル変数を使用します。しかしもちろん、これらはコードを少し奇妙にします。

于 2013-05-09T06:15:43.947 に答える
0

あなたの編集に照らして、次のようなことを試すことができます:

#include <iostream>
#include <stdexcept>
using namespace std;

template <typename T>
class my_class
{
public:
  static bool s_isInitialized;

  my_class()
  {
    if(!s_isInitialized) {
      throw logic_error("must call my_function<T> before instantiating my_class<T>");
    }
    cout << "ctor ok" << endl;
  }
};

template <typename T>
bool my_class<T>::s_isInitialized = false;

template <typename T>
void my_function(const char * type)
{
  cout << "initializing " << type << endl;
  my_class<T>::s_isInitialized = true;
}

int main()
{
  my_function<bool>("bool");
  my_class<bool> a;

  my_function<int>("int");
  my_class<int> b;
  my_class<int> c;

  cout << "now for something different" << endl;
  my_class<short> d;        // ctor throws
  // this code isn't reached
}

my_class<T>基本的に、適切なテンプレートmy_function<T>が呼び出されたかどうかを判断するテンプレートのインスタンス化ごとにフラグを設定します。フラグを に初期化し、in にfalse設定します。コンストラクターはフラグをチェックし、それでも false の場合は例外をスローする必要があります。truemy_function<T>my_class<T>

チェックはコンパイル時ではなく実行時に行われますが、オーバーヘッドは無視できます。これはブール値の単一チェックであり、バイトをロードするための 1 つの命令と分岐のための 1 つの命令です。まともなハードウェア分岐予測子は、クラスのすべてのインスタンス化に対して常に正しく予測するため (最初のインスタンス化または 2 つのインスタンス化を除く)、分岐にオーバーヘッドを支払う必要はありません。

さらに、チェックインを追加しmy_function<T>て、フラグがまだ設定されていないことを確認し、logic_error初期化が既に行われている場合は何もしないか、(適切と思われる場合) 別のものをスローすることもできます。

スレッド化/同時実行を使用している場合は、もちろん、フラグ/初期化関数への同時アクセスから保護する必要があります。これにはプログラミングのオーバーヘッドが多少必要になりますが、難しくはありません。

于 2013-05-09T05:35:27.603 に答える