8

リモートサーバーに接続するのが仕事のクラスがあるとします。このクラスを抽象化して、UDP 経由で接続するバージョンと TCP 経由で接続するバージョンの 2 つのバージョンを提供したいと考えています。可能な限り無駄のないランタイム コードを構築したいと考えており、ポリモーフィズムを使用する代わりに、テンプレートを検討しています。これが私が想定しているものですが、これが最善の方法であるかどうかはわかりません。

class udp {};
class tcp {};

template<class T,typename X>
class service
{
private:
  // Make this private so this non specialized version can't be used
   service();
};

template<typename X>
class service<udp, X>
{
private:
   udp _udp;
   X _x;
};

template<typename X>
class service<tcp, X>
{
private:
   tcp _tcp;
   X _x;
};

したがって、最終的な利点は、T の汎用性が引き続き利用できることですが、UDP または TCP 接続をセットアップするために必要な非常に異なるコードが特化されています。両方を 1 つのクラスに入れるか、IConnectionManager などのネットワーク接続を設定するための純粋な仮想インターフェイスに準拠する別のクラスを提供できると思います。

しかし、これにより、ジェネリック T のコードを両方の特殊化されたバージョンで記述して維持する必要があり、最終的には同じであるという問題が残ります。これに対処するにはどうすればよいですか?私はこれについてすべて間違っていると感じています。

4

4 に答える 4

13

これは、トランスポート プロトコルのポリシーを使用して行うのが最適です。

template<typename Transport>
class service : Transport {
public:
    typedef Transport transport_type;

    // common code
    void do_something() { 
        this->send(....);
    }
};

class tcp {
public:
    void send(....) {

    }
};

class udp {
public:
    void send(....) {

    }
};

typedef service<tcp> service_tcp;
typedef service<udp> service_udp;

これもポリモーフィックであることに注意してください。これはコンパイル時ポリモーフィズムと呼ばれます。ポリシーを基本クラスに入れると、Empty-Base-Class-Optimization の恩恵を受けます。つまり、基本クラスはスペースを取る必要はありません。ポリシーをメンバーとして配置すると、常にそのメンバーに委任する必要があり、時間の経過とともに面倒になる可能性があるという別の欠点があります。本Modern C++ Design では、このパターンについて詳しく説明しています。

理想的には、トランスポート プロトコルは、その上のプロトコルについて何も知る必要はありません。ただし、何らかの理由でそれに関する情報を取得する必要がある場合は、crtp パターンwikiを使用できます。

template<template<typename Service> class Transport>
class service : Transport<service> {

    // since we derive privately, make the transport layer a friend of us, 
    // so that it can cast its this pointer down to us. 
    friend class Transport<service>;

public:
    typedef Transport<service> transport_type;

    // common code
    void do_something() { 
        this->send(....);
    }
};

template<typename Service>
class tcp {
public:
    void send(....) {

    }
};

template<typename Service>
class udp {
public:
    void send(....) {

    }
};

typedef service<tcp> service_tcp;
typedef service<udp> service_udp;

テンプレートをヘッダーに配置する必要はありません。それらを明示的にインスタンス化すると、含める必要のあるコードがはるかに少なくなるため、コンパイル時間が短縮されます。これをservice.cppに入れます:

template class service<tcp>;
template class service<udp>;

ここで、サービスを使用するコードは、サービスのテンプレート コードについて知る必要はありません。そのコードは、service.cpp のオブジェクト ファイルに既に生成されているためです。

于 2008-12-10T15:58:36.663 に答える
4

興味深い繰り返しテンプレート パターン、別名 Five Point Palm Exploding Alexandrescu Technique を使用します。

template <typename Underlying>
class Transmit
{
public:
   void send(...)
   {
      _U.send(...)
   };

private:
    Underlying _U;
};

class Tcp
{
public:
   void send(...) {};
};

class Udp
{
public:
   void send(...) {};
};

おそらくもっと多くのテンプレート パラメーターとサブクラスが存在するでしょうが、静的メソッドを使用することもできます。

ところで、テンプレート コードは一般的に効率的ですが、サイズも大きくなります。

于 2008-12-10T14:48:23.217 に答える
2

テンプレートは必要ありません (解決策はありますが)。これは、コンストラクター経由ではなく、テンプレート経由の依存性注入です。個人的には、コンストラクターを介して行います。しかし、テンプレートを介して行うと、安価なメソッド呼び出しの疑わしい利点が得られます(仮想である必要はありません)。ただし、コンパイラの最適化も容易になります。

udp オブジェクトと tcp オブジェクトの両方が、引き続き同じインターフェイスをサポートする必要があります
継承を介して行う場合、両方が共通のインターフェイス (仮想基本クラス) を実装する必要があります。これはテンプレートを介して行われますが、これは必須ではありませんが、コンパイラは Service オブジェクトが必要とする同じメソッド呼び出しをサポートすることを確認します。

元の質問で尋ねられたように、部分的なテンプレートの特殊化の明示的な必要性 (または利点) はありません (説明されている状況では)。

テンプレート方式

class udp {/*Interface Plop*/static void plop(Message&);};
class tcp {/*Interface Plop*/static void plop(Message&);};
template<typename T>
class Service
{
    public:
        void doPlop(Message& m) { T::plop(m);}
    // Do not actually need to store an object if you make the methods static.
    // Alternatively:
    public:
        void doPlop(Message& m) { protocol.plop(m);}
    private:
        T protocol;
};

ポリモーフィック バージョン

class Plop{virtual void plop(Message&) = 0;} // Destruct or omitted for brevity
class upd:public Plop {/*Interface Plop*/void plop(Message&);};
class tcp:public Plop {/*Interface Plop*/void plop(Message&);};
class Service
{
    public:
        Service(Plop& p):protocol(p)  {};
        void doPlop(Message& m) { protocol.plop(m);}
    private:
        Plop& protocol;
};
于 2008-12-10T17:42:24.817 に答える
0

ポリモーフィズムまたはテンプレートの特殊化のいずれかを選択する際の主なポイントは、少なくともこの特定のケースでは、実行時またはコンパイル時に使用する動作を選択するかどうかだと思います。
たとえば、ユーザーに提供された接続文字列に基づいて udp または tcp 接続が必要な場合は、ポリモーフィズムがニーズに最適です。具体的なクラスを作成し、それを基本インターフェイスへのポインターを処理する汎用コードに渡します。
それ以外の場合は、テンプレートの使用を検討してください。テンプレートの特殊化が必要かどうかはわかりません。

お役に立てれば :)

于 2008-12-10T15:07:42.257 に答える