15

クラス型を自動的に登録するための設計パターンまたはイディオムが存在するかどうか疑問に思っていました。または、単純に、基本クラスを拡張するだけで、クラスでメソッドを強制的に呼び出すことはできますか?

たとえば、基本クラスAnimalと拡張クラスTigerおよびがありDog、 を拡張するすべてのクラスを出力するヘルパー関数があるとしますAnimal

だから私は次のようなものを持つことができます:

struct AnimalManager
{
   static std::vector<std::string> names;
   static void registerAnimal(std::string name) { 
            //if not already registered
            names.push_back(name); }
};

struct Animal
{
   virtual std::string name() = 0;
   void registerAnimal() { AnimalManager::registerAnimal(name()); }
};
struct Tiger : Animal
{
   virtual std::string name() { return "Tiger"; }
};

したがって、基本的には次のようにします。

Tiger t;
t.registerAnimal();

staticこれも関数に組み込むことができます。registerAnimalメソッドを明示的に呼び出すことなくこれを達成するのに役立つパターン(不思議なことに再帰的なテンプレートなど)またはそのようなものはありますか?

私は将来拡張可能になり、class Animalの人が を呼び出すのを忘れる可能性があることを望んregisterでいます。

PS これは単なる例であり、実際に動物を実装しているわけではありません。

4

6 に答える 6

16

これは、興味深い再帰的なテンプレート イディオムを使用して行うことができます。コンパイラによって強制できないクラスを拡張している人からは何も必要ありません。

template<class T>
struct Animal
{
   Animal()
   { 
      reg;  //force specialization
   }
   virtual std::string name() = 0;
   static bool reg;
   static bool init() 
   { 
      T t; 
      AnimalManager::registerAnimal(t.name());
      return true;
   }
};

template<class T>
bool Animal<T>::reg = Animal<T>::init();

struct Tiger : Animal<Tiger>
{
   virtual std::string name() { return "Tiger"; }
};

このコードでは、Animal特殊化する場合にのみ拡張できます。staticコンストラクターはメンバーを強制的regに初期化し、次に register メソッドを呼び出します。

編集:コメントで@David Hammenが指摘したように、Animalオブジェクトのコレクションを持つことはできません。ただし、これは、テンプレートの継承元となる非テンプレート クラスを用意し、それを基本クラスとして使用し、テンプレートのみを拡張に使用することで簡単に解決できます。

于 2012-04-26T12:41:35.813 に答える
8

すべての動物を登録する必要があると主張する場合は、コンストラクターnameのパラメーターを作成してみませんかAnimal。次に、登録の問題をAnimalコンストラクターに配置すると、すべての派生物が有効な名前を渡して登録する必要があります。

struct Animal
{
   Animal(std::string name){ AnimalManager::registerAnimal(name);}
}

struct Tiger : Animal
{
   Tiger():Animal("Tiger"){}
};
于 2012-04-26T12:31:11.890 に答える
4

これは、オブジェクトの構築時にある種の簿記を行いたい典型的な例です。Scott Meyers の「Effective C++」の項目 9 に、この例が示されています。

基本的に、すべての簿記を基本クラスに移動します。派生クラスは、基本クラスを明示的に構築し、基本クラスの構築に必要な情報を渡します。例えば:

struct Animal
{
  Animal(std::string animal_type)
  {
    AnimalManager::registerAnimal(animal_type);
  };
};

struct Dog : public Animal
{
  Dog() : Animal(animal_type()) {};


  private:      
    static std::string animal_type()
    {
      return "Dog";
    };
};
于 2012-04-26T12:35:56.130 に答える
2

通常、私はこれをマクロで行います。

単体テスト フレームワークは、多くの場合、GoogleTestなどのテストを登録するための手法を採用しています。

于 2012-04-26T12:29:52.303 に答える
2

@AMCoder: これはあなたが望む答えではありません。あなたが望む答え、リフレクション(例: what_am_I()) は C++ には実際には存在しません。

C++ は、RTTI を介してかなり制限された形式になっています。基本クラスで RTTI を使用して、構築中のオブジェクトの「真の」型を判別することはできません。基本クラスを呼び出すtypeid(*this)と、代わりtypeinfoに構築中のクラスの が得られます。

Animalクラスにデフォルト以外のコンストラクタのみを持たせることができます。これは から直接派生するクラスでは問題なく機能Animalしますが、派生クラスから派生するクラスについてはどうでしょうか。このアプローチでは、それ以上の継承を排除するか、クラス ビルダーが複数のコンストラクターを作成する必要があり、そのうちの 1 つが派生クラスで使用されます。

Luchian の CRTP ソリューションを使用することもできますが、これも継承に問題があり、Animalオブジェクトへのポインターのコレクションを持つこともできません。その非テンプレート基本クラスをミックスに追加して、 のコレクションを作成Animalし、元の問題をもう一度発生させることができます。から派生するクラスを作成するためにのみテンプレートを使用するようにドキュメントに記載する必要がありますAnimal。誰かがそれをしないとどうなりますか?

最も簡単な解決策は、あなたが気に入らないものです: から派生するクラスのすべてのコンストラクターが をAnimal呼び出さなければならないことを要求しますregister_animal()。ドキュメントでそう言ってください。コードのユーザーにいくつかの例を示します。そのコード例の前にコメントを入れて// Every constructor must call register_animal().ください。あなたのコードを使用する人はとにかくカット アンド ペーストを使用するので、カット アンド ペーストの準備が整ったソリューションを手元に用意してください。

ドキュメントを読まないとどうなるか心配するのは、時期尚早の最適化です。コンストラクターですべてのクラス呼び出しを要求することregister_animal()は、単純な要件です。誰もがそれを理解し、誰もが簡単に実装できます。ユーザーがこの簡単な指示に従うことさえできない場合、登録の失敗よりもはるかに大きな問題が発生します。

于 2012-04-26T14:21:06.227 に答える
1

次のように、派生クラスがインスタンス化されるたびに呼び出される基本クラス コンストラクターでメソッドを呼び出すことができます。

class Animal {
public:
    Animal() {doStuff();}
}

doStuff() メソッドは、静的操作を行うために基本クラスに実装することも、純粋仮想化して派生クラスに実装することもできます。

編集:コメントで正しく指摘されているように、仮想メソッドはctorで呼び出すことはできません。

ただし、基本クラスのコンストラクターは派生コンストラクターの前に呼び出されることに注意してください。したがって、次のようなこともできます。

class Animal {
public:
    Animal(const std::string &name) {doStuff(name);}

private:
    Animal(); // Now nobody can call it, no need to implement
}

class Dog : public Animal {
    Dog() : Animal("Dog") {}
}

それが役立つことを願っています

于 2012-04-26T12:31:13.933 に答える