11

CRTPでは、マクロを使用せず、書き出すことなく、コンストラクターを派生クラスにクリーンに挿入したいと考えています。無理そうなので、いくつか回避策を考えてみました。

まず、すべての派生クラスに対して一意の整数型タグを持つ必要がある、基になるイベント クラス (QEvent)があります (論拠を参照)。これを取得するには、登録関数を呼び出します。これを非表示にする CRTP ラッパーを作成するのは簡単です。

template <typename Derived> class EventWrapper : public QEvent {
public:
    EventWrapper() : QEvent(staticType()) {}
    static QEvent::Type staticType() {
        static QEvent::Type type = static_cast<QEvent::Type>(registerEventType());
        return type;
    }
};
class MyEvent1 : public EventWrapper<MyEvent1> {}; // easy-peasy
class MyEvent2 : public EventWrapper<MyEvent2> {};

: は、呼び出されるたびに一意の型を返すことに注意してくださいMyEvent1::staticType() != MyEvent2::staticType()registerEventType()

ここで、イベント クラスにいくつかのデータを運ぶようにします。

template <typename Derived> class StringEvent : public EventWrapper<D> {
    std::string m_str;
public:
    explicit StringEvent(const std::string & str) : m_str(str) {}
    std::string value() const { return m_str; }
};

しかし、ここで問題が発生します。派生クラスでコンストラクターを手動で定義する必要があります。ここでの要点は、文字列を運ぶさまざまなイベント タイプが多数存在する可能性があるため、このようなクラスの作成は簡単であるべきだということです。しかし、それは簡単ではありません。

class MyEvent3 : public StringEvent<MyEvent3> {
    public: MyEvent3(std::string s) : StringEvent(s) {}
};

これは、C++11 コンストラクター転送を使用しても、明らかにすぐに古くなります。

class MyEvent3 : public StringEvent<MyEvent3> { using StringEvent::StringEvent; };

私たちが望むのは、このコンストラクターを派生クラスに注入する方法、または使いやすさを提供しながらそれを回避する方法です。確かにプリプロセッサ マクロでそれを隠すことができますが、私はそれらのマクロが嫌いです。非常に単純な概念に新しい名前を導入するため、メンテナンスが面倒です。

もちろんダミータイプも使えます。ダミー型の定義は必要ないことに注意してください。型引数として使用されるのは名前だけです。

// Pre-C++11
class DummyEvent3;
typedef StringEvent<DummyEvent3> MyEvent3;
// C++11
class DummyEvent3;
using MyEvent3 = StringEvent<DummyEvent3>;

もう 1 つの解決策は、intテンプレート引数を使用して列挙値を使用することですが、これによりregisterEventType()、最初に を使用することで解決された一意性の問題が再び発生します。大規模なプログラムが正しいことを保証するのは楽しいことではありません。そして、列挙型を綴る必要があります。

そこで、メタファクトリーと呼ぶメタプログラム クラスを思いつきました。これは、StringEventすべてを 1 つの型定義に保ちながら、すぐに使用できるクラスを生成できます。

// the metafactory for string events
template <typename Derived> class StringEventMF {
public:
    class Event : public EventWrapper<Derived> {
        std::string m_str;
    public:
        explicit Event(const std::string & val) : m_str(val) {}
        std::string value() const { return m_str; }
    };
};

または単に

template <typename Derived> class StringEventMF {
public:
    typedef StringEvent<Derived> Event;
};

これは次のように使用されます。

class Update : public StringEventMF<Update> {};
class Clear : public StringEventMF<Clear> {};

void test() {
   Update::Event * ev = new Update::Event("foo");
   ...
}

使用するクラスはUpdate::EventClear::Eventです。Updateとはメタファクトリです。Clear目的のイベント クラスを生成します。メタファクトリからの派生は、具象クラス型からの派生を回避します。メタファクトリ型は、一意の具体的なクラス型を作成するために必要な一意の型識別子を提供します。

質問は次のとおりです。

  1. それを行う「よりクリーンな」または「より望ましい」方法はありますか?理想的には、次の動作しない疑似コードが私の理想的な方法です-繰り返しはありません:

    class UpdateEvent : public StringEvent <magic>;
    

    派生クラスの名前は 1 回StringEventだけ表示され、基本概念の名前も 1 回だけ表示されます。CRTP では、クラス名を 2 回表示する必要があります。これまでのところ、許容できると思いますが、私のメタプログラミング フーはボロボロです。繰り返しますが、プリプロセッサのないソリューションが必要です。それ以外の場合は簡単です。

  2. metafactoryという名前は私のオリジナルの発明 (笑) ですか、それとも単に私の google-Fu が欠けているだけですか? このメタファクトリー パターンは非常に柔軟なようです。複数の派生によってメタファクトリーを構成するのは簡単です。Update::Eventある工場でUpdate::Foo作られ、別の工場で作られたいとしましょう。

この質問は、この回答によって動機付けられています。注: 実際のコードでは を使用しますQStringが、できるだけ一般的なものにしようとしています。

4

2 に答える 2