Является ли частичная специализация шаблона класса ответом на эту проблему проектирования?
Скажем, у вас есть класс, который занимается подключением к удаленному серверу. Я хочу абстрагировать этот класс для предоставления двух версий, одна из которых подключается через 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 ответа
Лучше всего это сделать, используя политику для транспортного протокола:
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;
Обратите внимание, что это также полиморфно. Это называется полиморфизмом времени компиляции. Помещение политики в базовый класс выиграет от оптимизации пустого базового класса. То есть вашему базовому классу не нужно занимать никакого пространства. Помещение политики в качестве члена имеет другой недостаток, заключающийся в том, что вы всегда должны делегировать вещи этому члену, что со временем может раздражать. Книга Modern C++ Design подробно описывает этот шаблон.
В идеале транспортный протокол не должен ничего знать о протоколе над ним. Но если по какой-то причине вам нужно получить некоторую информацию об этом, вы можете использовать шаблон wtr crtp:
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.
Я бы использовал любопытный рекурсивный шаблон, известный как "Техника взрыва ладони с пятью точками":
template <typename Underlying>
class Transmit
{
public:
void send(...)
{
_U.send(...)
};
private:
Underlying _U;
};
class Tcp
{
public:
void send(...) {};
};
class Udp
{
public:
void send(...) {};
};
Вероятно, будет гораздо больше параметров шаблона и подклассов, но вы поймете, что вы также можете использовать статические методы.
Между прочим, код шаблона, как правило, более эффективен, но и намного больше.
Шаблоны не нужны (хотя возможное решение). Это просто внедрение зависимостей через шаблоны, а не через конструктор. Лично я бы сделал это через конструктор. Но выполнение этого с помощью шаблона дает вам сомнительную выгоду от более дешевого вызова метода (он не обязательно должен быть виртуальным). Но также позволяет упростить оптимизацию компилятора.
И объекты 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;
};
Я думаю, что основной момент при выборе между полиморфизмом или специализацией шаблона, по крайней мере, в этом конкретном случае, заключается в том, хотите ли вы выбрать, какое поведение использовать во время выполнения или во время компиляции.
Если вы хотите, чтобы соединение udp или tcp базировалось, например, на строке соединения, предоставленной пользователем, тогда полиморфизм лучше всего соответствует вашим потребностям; создайте конкретный класс и затем передайте его универсальному коду, который обрабатывает указатель на базовый интерфейс.
В противном случае, вы можете рассмотреть возможность использования шаблонов - я не уверен, если вам нужна специализация шаблонов.
Надеюсь это поможет:)