Является ли частичная специализация шаблона класса ответом на эту проблему проектирования? - PullRequest
7 голосов
/ 10 декабря 2008

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

Ответы [ 4 ]

12 голосов
/ 10 декабря 2008

Лучше всего это сделать, используя политику для транспортного протокола:

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;

Обратите внимание, что это также полиморфно. Это называется полиморфизмом времени компиляции. Помещение политики в базовый класс выиграет от оптимизации пустого базового класса. То есть вашему базовому классу не нужно занимать никакого пространства. Помещение политики в качестве члена имеет другой недостаток, заключающийся в том, что вы всегда должны делегировать вещи этому члену, что со временем может раздражать. Книга Современный дизайн C ++ подробно описывает этот паттерн.

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

3 голосов
/ 10 декабря 2008

Я бы использовал любопытный шаблон для выздоровления, он же Five Point Palm Exploding Alexandrescu Техника:

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

private:
    Underlying _U;
};

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

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

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

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

2 голосов
/ 10 декабря 2008

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

И объекты 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;
};
0 голосов
/ 10 декабря 2008

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

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

...