Является ли частичная классификация шаблонов ответом на эту проблему дизайна?

Скажите, что у вас есть класс, которому нужно подключиться к удаленному серверу. Я хочу отделить этот класс, чтобы предоставить две версии: одну, которая соединяется через UDP, а другая через TCP. Я хочу построить самый скудный код времени исполнения, и вместо использования полиморфизма я рассматриваю шаблоны. Вот что я представляю, но я не уверен, что это лучший способ сделать это:

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-соединения, был специализированным. Я полагаю, вы могли бы поместить его в один класс или предоставить другой класс, который придерживается некоторого чистого виртуального интерфейса для настройки сетевого подключения, например IConnectionManager.

Но это оставляет проблему кода для того, чтобы общий T теперь должен был быть написан и поддерживаться в обеих специализированных версиях, где они в конечном счете одинаковы. Как лучше всего это решить? У меня такое чувство, что я все это делаю неправильно.

Ответ 1

Это лучше всего сделать с помощью политики для транспортного протокола:

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. То есть ваш базовый класс не должен занимать какое-либо место. Включение политики в качестве члена имеет другой недостаток, который вы всегда должны делегировать вещи этому члену, что может раздражать со временем. В книге Modern С++ 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.

Ответ 2

Я бы воспользовался любопытным шаблоном шаблона повторения, который называется 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(...) {};
};

Вероятно, будет гораздо больше параметров шаблона и подклассов, но вы получите эту идею, вы также можете использовать статические методы.

Кстати, код шаблона обычно более эффективен, но также намного больше.

Ответ 3

Шаблоны не нужны (хотя это возможное решение). Это просто инъекция зависимостей через шаблоны, а не через конструктор. Лично я бы сделал это через конструктор. Но делать это с помощью шаблона дает вам сомнительный benifit более дешевый вызов метода (он не обязательно должен быть виртуальным). Но также позволяет упростить оптимизацию компилятора.

Оба объекта udp и tcp должны поддерживать тот же интерфейс.
Если вы делаете это через наследование, они должны оба реализовать общий интерфейс (виртуальный базовый класс), это делается с помощью шаблонов, это не обязательно, но компилятор проверяет, поддерживают ли они те же вызовы методов, что и объект службы.

Как было задано в исходном вопросе, я не вижу явной необходимости (или пользы) для частичной специализированности шаблонов (в описанной ситуации).

Метод шаблона

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;
};

Ответ 4

Я думаю, что основным моментом в выборе полиморфизма или специализации шаблонов, по крайней мере, в этом конкретном случае, является выбор того, какое поведение следует использовать во время выполнения или во время компиляции.
Если вы хотите иметь соединение udp или tcp, основанное, например, на строке подключения, предоставленной пользователю, то полиморфизм наилучшим образом соответствует вашим потребностям; создайте конкретный класс, а затем передайте его в общий код, который обрабатывает указатель на базовый интерфейс.
В противном случае вы можете использовать шаблоны - я не уверен, нужна ли вам спецификация шаблона.

Надеюсь, что это поможет:)