0

現在、実行時に一意のファミリ ID をクラスに割り当てることができるシステムを作成しようとしています。基本的に、実行時にクラスを登録した後、整数値に基づいてクラスを区別できるようにしたいと考えています。

これの使用例は、このシステムがコンポーネント システムの事務処理として使用されることです。すべてのクラスは、クラス Component の子孫 (必ずしも直接である必要はありません) です。実行時に登録されます。

いくつかの理由でこれを実行できるようにしたい:

  • 拡張の容易さと安全性: コンポーネントを追加するために、基本コンポーネント システムの巨大なリストを変更する必要はありません。
  • 効率: ルックアップは内部でこの整数値を使用して行われるため、検索ルックアップの代わりに O(1) にすることができます。これらのコンポーネントはシステムの大部分を構成するため、インスタンス変数にはしないことを好みます。テストケースでは、> O(1) の削除と挿入を行う余裕がないことがすでに示されているため、この側面を無視することはできません。(最初の実装では、マップ ルックアップ テーブルを使用しました)

コンパイル時にチェックされる実装を探しています。契約ベースのソリューションではありません。Java での契約ベースのソリューションは次のようになります。

interface Component {

   // Should return the value of a static variable
   int getFamilyID();
   int setFamilyID(int id);
}

class Foo implements Component {

   static int familyID = 0;

   int getFamilyID(){ return familyID; }
   int setFamilyID(int id){ familyID = id; }
}

class System {  // Singleton
   static int registeredComponents = 0;
   void register(Component c){ c.setFamilyID(registeredComponents++); }   
}

これは明らかに 2 つの理由で機能しません。

  • 変数を public にしないと A.getFamilyID() を指定できません。ユースケース: c.getFamilyID() == Foo.getFamilyID(); (インスタンスの代わりに)
  • いたるところに冗長なコード。Component の各実装には、コピーして貼り付けた実装が必要です。

C++なら静的変数を指定したテンプレートで解決できると思っていたのですが、コンポーネントの直系の子孫ではないクラスだと使えなくなります。

また、Java 内で列挙型を使用することはできません。列挙型は言語固有であり、コンポーネントの量によって 1 つのファイルのコードが膨大になるためです。(また、それらはすべて1か所で指定する必要があります)

この問題の助けや、私が「間違ったことをしようとしている」(TM)理由についての洞察は、非常に役立ちます:-)

編集:明確にするために、コンポーネントクラスで設定できる静的整数のコード規則をコンパイル時に確実にする方法が必要です。

4

2 に答える 2

1

基本的に求めているのは、コンパイル時に特定の実行時の動作をチェックすることです。一般的なケースでは、それは単純に不可能です。必要なすべての関数を書くことはできますが、コンパイラーは、指定された関数を型ごとに 1 回だけ呼び出すことを保証することはできません。あなたができる最善の方法は、ある種の静的変数を使用し、関数をプライベートにして、コンストラクターで登録への呼び出しを配置することです。

class Component
{
protected:
    class Registrator
    {
        static int nextId;
        int id;
    public:
        Registrator() ; id( nextId ++ ) {}
        int id() const { return id; }
    };
    //  ...
};

class Derived ; public Component
{
    static Registrator ourId;
    //  ...
};

(これは Java でも実行できます。static Registrator ourId = new Registrator();各派生クラスに静的ブロックを配置するだけです。)

各派生クラスに type の static メンバーが 1 つだけ含まれていることを (契約により) 要求する必要がありますRegistrator。逆に、これを避けることはできないと思います。

一般に、基本クラスと派生クラスがあるときはいつでも、コントラクトに頼る必要があることに注意してください。たとえば、基本クラスに仮想関数cloneがある場合 (通常のセマンティクスを使用)、すべての派生クラスはそれを実装する必要があります。コンパイル時にこの種のことを強制する方法はありません。コントラクトのイディオムによるプログラミングの一部では、動的なオブジェクトのclone戻り値が正しいことを実行時に強制できますが、実行時であっても、返されたオブジェクトが実際のコピーであり、まったく無関係なインスタンスではないことを強制する方法はありません。

私が言えることは、これが実際に問題になることは一度もなかったということだけです。(または他の基本クラス)から派生した人Componentは、 によって約束されたコントラクトを知っている必要があり Componentます。いくつかのことを検証することはできます (すべきです) が、最終的にはすべてを検証することはできません。それについて。(ここではコード レビューが大いに役立ちます。特に、テスト カバレッジも含むコード レビューでは、すべての契約の問題をテストする必要があります)。

編集:

int最後に 1 つのコメント:識別子にan を使用することに反対します。識別子を比較する際のパフォーマンスが重要な場合でも、char const[];を使用できます。正しく取得されたすべての識別子がchar const*同じ文字列を指すことを保証する場合 (使用する実際の識別子は a であるため)、ポインタを比較するだけで済みます。派生クラスのコントラクトは次のとおりです。

class Derived : public Component
{
public:
    static char const* className() { return "Derived"; }
    //  overriding virtual function in Component...
    char const* type() const { return className(); }
    //  ...
};

次に、char const*返された をclassNameまたは type識別子として使用します。

派生クラスの作成者がタイプするのはもう少しですが、少なくとも C++ では、それを単純化するためのマクロが常に存在します。実際、上記の元の解決策であっても、この種のことにはマクロをお勧めします。派生クラスがすべてマクロを使用している場合は、他に何も変更せずに戦略を変更できます。

于 2013-01-05T11:02:54.483 に答える
0

C++ では、コードの繰り返しを避けるために、不思議なことに繰り返されるテンプレート パターンを使用できます。

class Component 
{
public:
    virtual ~Component() {}
    virtual int getFamilyId() const = 0;
};

// each instance is assigned a unique int at construction
class FamilyId 
{ 
    static int numberOfExistingIds = 0;
    int id;
public:
    FamilyId() : id( numberOfExistingIds++ ) {}
    int getId() const { return id; }
};

// implementation is done only in this template class
template <typename Derived, typename Base = Component> 
class ComponentImpl : public Base 
{
    static FamilyId familyId; // one instance per class for unique id
public:
    virtual int getFamilyId() const 
    { 
        assert( typeid(*this) == typeid(Derived) ); 
        return familyId.getId(); 
    }
};

これを設定すると、Component クラス階層に新しいクラスを簡単に作成できます。

// first derived class, automagically implemented by template magic
class MyGeneralComponent 
    : public ComponentImpl<MyGeneralComponent> 
{
     /* add new methods here */ 
};

// class further down in the hierarchy are also possible, 
// by using the second template argument. The implementation still works. 
class MySpecificComponent 
    : public ComponentImpl<MySpecificComponent,MyGeneralComponent> 
{
     /* add new methods here */ 
};

テンプレートから正しく派生したassert(...)場合、 は実行時に自動的にチェックします。したがって、次のようなバグを発見できます

class MySpecificComponent : MyGeneralComponent 
{
};

実行時に。それ以外の場合、この派生クラスは直接のベースと同じインターフェイス実装を使用し、同じ静的変数を使用しますが、これはバグです。

手でクラスを登録する必要がないことに気付いたかもしれません。これは、main() 関数が開始する前に静的変数を動的に初期化することによって行われます。したがって、それについて何もする必要はありません。このようにして、他のファイルを変更したり、多くのコードを繰り返したりすることなく、クラスを 1 つの場所に簡単に実装できます。

于 2013-01-05T11:08:40.977 に答える