3

またはクラスMessage&のいずれかを参照するオブジェクトを受け入れられるようにしたい。オブジェクトの基になるタイプに基づいて、またはを作成できるようにしたい。たとえば、以下を参照してください。Message1Message2MessageWithData<Message1>MessageWithData<Message2>Message&

class Message {};
class Message1 : public Message {};
class Message2 : public Message {};

template<typename Message1or2>
class MessageWithData : public Message1or2 { public: int x, y; }

class Handler()
{
public:
  void process(const Message& message, int x, int y)
  {
    // create object messageWithData whose type is 
    // either a MessageWithData<Message1> or a MessageWithData<Message2> 
    // based on message's type.. how do I do this?
    //
    messageWithData.dispatch(...)
  }
};

messageWithDataクラスには、基本的にMessageから継承されたメソッドが含まれており、そのタイプに基づいて、動的にダブルディスパッチしてハンドラーに戻すことができます。これまでの私の最善の解決策は、データをメッセージタイプから分離し、動的ディスパッチチェーン全体に渡すことでしたが、メッセージタイプに可変データ。

(私が多かれ少なかれフォローしている方法は、http://jogear.net/dynamic-double-dispatch-and-templatesからのものです)

4

4 に答える 4

3

ランタイムとコンパイル時の概念、つまり(ランタイム-)ポリモーフィズムとテンプレートを混在させようとしています。申し訳ありませんが、それは不可能です。

テンプレートは、静的型とも呼ばれるコンパイル時に型を操作します。の静的タイプmessageMessage、ですが、動的タイプはまたはのいずれMessage1Message2です。テンプレートは動的タイプについて何も知らず、それらを操作することはできません。実行時ポリモーフィズムまたはコンパイル時ポリモーフィズム(静的ポリモーフィズムとも呼ばれる)のいずれかを使用します。

ランタイムポリモーフィズムアプローチは、ダブルディスパッチを使用したビジターパターンです。CRTPイディオムを使用したコンパイル時のポリモーフィズムの例を次に示します。

template<class TDerived>
class Message{};

class Message1 : public Message<Message1>{};
class Message2 : public Message<Message2>{};

template<class TMessage>
class MessageWithData : public TMessage { public: int x, y; };

class Handler{
public:
  template<class TMessage>
  void process(Message<TMessage> const& m, int x, int y){
    MessageWithData<TMessage> mwd;
    mwd.x = 42;
    mwd.y = 1337;
  }
};
于 2011-05-26T23:17:28.277 に答える
2

あなたが持っている

void process(const Message& message, int x, int y)
{
  // HERE
  messageWithData.dispatch(...)
}

ここでは、またはのインスタンスであるかどうかに応じて、MessageWithData<Message1>またはのいずれかを作成します。MessageWithData<Message2>messageMessage1Message1

ただし、クラステンプレートはコンパイル時に何をすべきかMessageWithData<T>を知る必要があるため、これを行うことはできませんが、そのタイプは、にディスパッチすることにより、実行時までコードのその時点で使用できません。Tmessage

于 2011-05-26T23:23:52.880 に答える
1

Xeoが言うように、この特定のケースではおそらくこれを行うべきではありません-より良い設計の代替案が存在します。とはいえ、RTTIを使用してそれを行うことはできprocess()ますが、新しい派生クラスが追加されたときに更新する必要がある一元化されたメンテナンスポイントになるため、一般的に眉をひそめます。これは見落とされがちで、実行時エラーが発生しやすくなります。

何らかの理由でこれを説得する必要がある場合は、少なくとも機能を一般化して、次のように、単一の関数がRTTIベースの実行時型決定を使用して任意の動作を呼び出すようにします。

#include <iostream>
#include <stdexcept>

struct Base
{
    virtual ~Base() { }

    template <class Op>
    void for_rt_type(Op& op);
};

struct Derived1 : Base
{
    void f() { std::cout << "Derived1::f()\n"; }
};

struct Derived2 : Base
{
    void f() { std::cout << "Derived2::f()\n"; }
};

template <class Op>
void Base::for_rt_type(Op& op)
{
    if (Derived1* p = dynamic_cast<Derived1*>(this))
        op(p);
    else if (Derived2* p = dynamic_cast<Derived2*>(this))
        op(p);
    else
        throw std::runtime_error("unmatched dynamic type");
}

struct Op
{
    template <typename T>
    void operator()(T* p)
    {
        p->f();
    }
};

int main()
{
    Derived1 d1;
    Derived2 d2;
    Base* p1 = &d1;
    Base* p2 = &d2;
    Op op;
    p1->for_rt_type(op);
    p2->for_rt_type(op);
}

上記のコードでは、独自のOpに置き換えて、同じランタイムからコンパイル時へのハンドオーバーを実行できます。これを逆にファクトリメソッドと考えると役立つ場合とそうでない場合があります:-}。

説明したように、for_rt_type派生タイプごとに更新する必要があります。1つのチームが基本クラスを「所有」し、他のチームが派生クラスを作成する場合は特に苦痛です。多くのややハッキーなものと同様に、低レベルのエンタープライズライブラリのAPI機能としてではなく、プライベート実装をサポートする方が実用的で保守しやすくなっています。これを使用したいということは、通常、他の場所での設計が悪いことを示していますが、常にそうとは限りませOpん。場合によっては、非常に有益なアルゴリズムがあります。

  • コンパイル時の最適化、デッドコードの削除など。
  • 派生型は同じセマンティクスのみを必要としますが、詳細は異なる場合があります
    • たとえば、 Derived1::value_typeis int、 is-Derived2::value_typeは、doubleそれぞれのアルゴリズムを効率的にし、適切な丸めなどを使用できるようにします。同様に、共有APIのみが実行されるさまざまなコンテナタイプに対して。
  • テンプレートメタプログラミング、SFINAEなどを使用して、派生型固有の方法で動作をカスタマイズできます。

個人的には、このテクニックの知識と適用能力は(まれに)多型をマスターする上で重要な部分だと思います。

于 2011-05-27T01:21:57.913 に答える
1

前述のように、テンプレートをそのまま作成することはできません。

追加のパラメーターを渡すことに問題はありませんが、操作を簡単にするために、おそらくそれらを単一の構造にパックします。

確かに、クラス階層を拡張してこれらすべてをパターンDataにシューホーンするよりも、補足パラメーターを使用する方が慣用的だと思います。

デザインをパターンに合わせようとするのはアンチパターンです。適切な方法は、デザインに合うようにパターンを調整することです。

そうは言っても...


ソリューションにはいくつかの選択肢があります。継承は奇妙に思えますが、デザイン全体が手元になければ、それが最善の策かもしれません。

コンパイル時と実行時のポリモーフィズムを自由に混在させることはできないことはすでに述べました。私は通常、問題を回避するためにシムを使用します。

class Message {};
template <typename T> class MessageShim<T>: public Message {};
class Message1: public MessageShim<Message1> {};

スキームはシンプルで、両方の長所から利益を得ることができます。

  • Messageテンプレートではないということは、従来のOO戦略を適用できることを意味します
  • MessageShim<T>テンプレートであることは、従来のジェネリックプログラミング戦略を適用できることを意味します

完了すると、良くも悪くも、あなたが望むものを手に入れることができるはずです。

于 2011-05-27T06:47:33.923 に答える