0

いくつかの値(A、B)を登録する2つのクラス(たとえば、クラスARegister、クラスBRegister)を初期化したいと思います。これらの2つのクラスをsuper(?)クラス(たとえば、Class RegisterALL)から初期化します。

例:RegisterALLクラスはARegisterとBRegisterを初期化します。したがって、モジュールを使用する人は、ARegisterとBRegisterのオブジェクトを個別に作成する必要はなく、代わりにRegisterALLのオブジェクトを作成できます。

これらすべての登録をコンストラクターで行いたいと思います。したがって、実行する必要があるのは、RegisterALLクラスのオブジェクトを作成することだけであり、登録は自動的に行われます。

プロジェクトでは例外を使用しません。したがって、登録に何らかのエラーがある場合、それを知ることはできません。

登録を行う、またはコンストラクターに登録を行わせる個別のメンバー関数が必要ですか。

私はオブジェクト指向デザインの初心者です。デザインに問題があると感じます。正しいアプローチを示していただければ助かります。

4

2 に答える 2

3

これを行う最善の方法は、CRTP(不思議なことに繰り返されるテンプレートパターン)を使用することです。派生クラスARegisterおよびBRegisterは、基本クラスRegisterALLにテンプレート引数として自分自身を渡します。次のようになります。

class RegisterAll
{
public:
    template<class DerivedType>
    DerivedType *createType()
    {
        RegisterAll *r = (*(m_creators[DerivedType::id]))();
        return dynamic_cast<DerivedType *>(r); //static_cast will work too if you didn't make a mistake
    }
protected:
    static std::map<int,RegisterAll *(*)()> m_creators;
};

std::map<int,RegisterAll *(*)()> RegisterAll::m_creators = std::map<int,RegisterAll *(*)()>();

template<class Derived>
class CRTPRegisterAll : public RegisterAll
{
public:
    static bool register() 
    {
        RegisterAll::m_creators.push_back(std::make_pair(Derived::id,Derived::create);
        return true;
    }

private:
    static bool m_temp;
};

template<class Derived>
bool CRTPRegisterAll<Derived>::m_temp = CRTPRegisterAll<Derived>::register();

class RegisterA : public CRTPRegisterAll<RegisterA>
{
private:
    static RegisterA *create() 
    {
        //do all initialization stuff here
        return new RegisterA;
    }

public:
    static const int id = 0;
};

これで、の静的変数の初期化により、各派生型の作成者関数がのリストm_tempCRTPRegisterAllプッシュされます。RegisterAll一般に、すべての派生クラスについて知っていることはあまり良い設計RegisterAllではありません(それはあまり拡張可能ではありません)。このようにして、実際の作成メソッドを各派生クラスに実装でき、作成者関数がに自動的に登録されRegisterAllます。CRTPの主な利点の1つは、派生クラスが基本クラスのリストに自分自身を登録する方法を知る必要がないことです。それはそれらのために行われます。エラー処理に関しては、それも各派生クラスの作成者関数に実装できます。idの問題を処理するためのより良い方法がありますが、ここでは詳しく説明しません。より簡単な方法が必要な場合は、ファクトリデザインパターンについて読むことをお勧めします。

于 2012-06-06T01:19:40.607 に答える
3

オブジェクト間の何らかの関係はすでに決まっているようです。しかし、あなたは漠然と関係を説明するだけです。

が単純な封じ込めを使用している場合RegisterALL、非常に単純な関係になります。この関係は次のように表現される場合があります(ASCIIグラフィックを許してください)。

       +-------------+
       | RegisterALL |               --> := has
       +-------------+
          |       |
          v       v
+-----------+   +-----------+
| ARegister |   | BRegister |
+-----------+   +-----------+

利点は、2人の扶養家族の写真が非常に単純なことです。ただし、多くのオブジェクトを登録している場合、画像はオブジェクトRegisterALLの束に爆発しているように見え始めXRegisterます。

RegisterALLがとを含むことを意図している場合は、コンテナを維持できるように、とARegisterBRegister共通基本クラスを作成することをお勧めします。ARegisterBRegisterRegisterALL

      +-------------+      +------------------+            <>--> := aggregates
      | RegisterALL |<>--->| AbstractRegister |              
      +-------------+      +------------------+              |
                                    |                      _/_\_ := inherits
                                   / \
                               ___/___\___
                               |         |
                    +-----------+       +-----------+
                    | ARegister |       | BRegister |
                    +-----------+       +-----------+

RegisterALLいくつの新しいアイテムが登録されても、との間の関係AbstractRegisterは同じままであることがわかります。さらに一歩進んで、テンプレートから派生することができARegisterますBRegister

      +-------------+      +------------------+
      | RegisterALL |<>--->| AbstractRegister |
      +-------------+      +------------------+
                                    |
                                   / \
                                  /___\
                                    |
                                    |     +--------------+
                           +--------------| RegisterType |
                           |              +--------------+
                           | RegisterTemplate |
                           +------------------+

さて、オブジェクト指向デザインのレッスンはこれで終わりです。これは非常に迅速にコードに変換されます。簡単なことから始めましょう。RegisterType登録するさまざまなものを列挙します。RegisterTypeNameオーバーロードされた演算子を使用すると、コードは、を出力するときに数値ではなく文字列を出力できますRegisterType

enum RegisterType { A, B, MAX_RegisterType };

static inline std::string
RegisterTypeName (RegisterType t)
{
    static const char * names[] = { "A", "B" };
    return names[t];
}

static inline std::ostream &
operator << (std::ostream &output, RegisterType t)
{
    return output << RegisterTypeName(t);
}

AbstractRegisterこのタイプを照会するためのインターフェースを提供します。さらに、pokeインターフェイスにはデフォルトの実装が用意されています。C ++では、抽象型は仮想デストラクタを提供する必要があることに注意してください。

class AbstractRegister {
public:
    virtual ~AbstractRegister () {}
    virtual RegisterType type () const = 0;
    virtual void poke () { std::cout << "Poked " << type(); }
};

typedef std::unique_ptr<AbstractRegister> AbstractRegisterPtr;
static const AbstractRegisterPtr AbstractRegisterNullPtr;

RegisterALLクラスには、タイプのものを保持するためのコンテナがありますAbstractRegister。マップを使用して、登録と見なしRegisterTypeているAbstractRegisterインスタンスに関連付けます。RegisterALLシングルトンとして実装されます。つまり、それ自体のインスタンスは1つだけ許可されます。このaddメソッドは登録を実行し、このメソッドはfind登録されたインスタンスを見つけることを可能にします。コンストラクターの実装は、RegisterALLの定義が完了するまで延期されRegisterTemplateます。

class RegisterALL {
    template <RegisterType> friend class RegisterTemplate;
    typedef std::unique_ptr<RegisterALL> SelfPtr;
    typedef std::map<RegisterType, AbstractRegisterPtr> RegisterMap;
    void add (AbstractRegister *r) { all[r->type()] = AbstractRegisterPtr(r); }
    RegisterALL ();
public:
    static const SelfPtr & instance () {
        if (!one) new RegisterALL;
        return one;
    }
    const AbstractRegisterPtr & find (RegisterType t) const {
        RegisterMap::const_iterator i = all.find(t);
        return (i != all.end()) ? i->second : AbstractRegisterNullPtr;
    }
private:
    static SelfPtr one;
    RegisterMap all;
};

RegisterALL::SelfPtr RegisterALL::one;

RegisterTemplateクラスはから派生し、によってAbstractRegisterパラメータ化されますRegisterTypetypeテンプレートパラメータの値を返すことにより、仮想メソッドを実装します。また、シングルトンを採用していますが、インスタンスを公開していません。代わりに、そのインスタンスはによって管理されRegisterALLます。register_typeに自分自身を登録するメソッドを提供します。このインスタンスは、のメソッドをRegisterALL使用することによってのみ見つけることができます。findRegisterALL

template <RegisterType RT>
class RegisterTemplate : public AbstractRegister {
    RegisterType type () const { return RT; }
    void poke () {
        std::cout << "Poked " << RegisterTypeName(RT) << std::endl;
    }
    RegisterTemplate () {
        std::cout << "Created " << RegisterTypeName(RT) << std::endl;
    }
    ~RegisterTemplate () {
        std::cout << "Destroying " << RegisterTypeName(RT) << std::endl;
    }
public:
    static void register_type () {
        if (RegisterALL::instance()->find(RT)) {
            std::cout << "Already registered " << RegisterTypeName(RT)
                      << std::endl;
            return;
        }
        RegisterALL::instance()->add(new RegisterTemplate<RT>);
    }
};

コンストラクターは、列挙型を反復処理するRegisterALLヘルパーテンプレートを使用し、対応するをインスタンス化することで、すべてのをに登録します。register_allRegisterTypeRegisterTemplateRegisterTypeRegisterALL

template <unsigned X>
struct register_all {
    register_all () {
        RegisterTemplate<static_cast<RegisterType>(X)>::register_type();
        register_all<X+1>();
    }
};

template <> struct register_all<MAX_RegisterType> {};

inline RegisterALL::RegisterALL ()
{
    one = std::move(SelfPtr(this));
    register_all<0>();
}

したがって、試してみるには、次のコードを使用します。

RegisterALL::instance();                  // registers all RegisterType's
RegisterTemplate<B>::register_type();     // try to register B again
RegisterALL::instance()->find(A)->poke(); // poke at A

そしてこれは出力です:

Created A
Created B
Already registered B
Poked A
Destroying B
Destroying A

スマートポインタが登録済みのアイテムを自動的にクリーンアップする方法に注目してください。

于 2012-06-06T02:42:04.250 に答える