假设您有一个班级,他的工作是连接到远程服务器。我想抽象这个类来提供两个版本,一个通过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的代码问题现在必须在两个专用版本中编写和维护,它们最终都是相同的。如何最好地解决这个问题?我有一种感觉,我认为这一切都错了。
答案 0 :(得分:12)
最好使用传输协议的策略:
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的目标文件中。
答案 1 :(得分:3)
我会使用好奇的模板模板,又名五点棕榈爆炸亚历山大技术:
template <typename Underlying>
class Transmit
{
public:
void send(...)
{
_U.send(...)
};
private:
Underlying _U;
};
class Tcp
{
public:
void send(...) {};
};
class Udp
{
public:
void send(...) {};
};
可能会有更多的模板参数和子类,但是你可以理解,你也可以使用静态方法。
顺便说一下,模板代码通常更有效但也更大。
答案 2 :(得分: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;
};
答案 3 :(得分:0)
我认为选择polimorphism或模板特化的重点,至少在这种特殊情况下,如果你想选择在运行时或编译时使用哪种行为。
如果你想要一个udp或tcp连接,例如,在提供给用户的连接字符串上,那么polimorphism最适合你的需要;创建一个具体的类,然后将其传递给处理指向基接口的指针的通用代码
否则,您可能会考虑使用模板 - 我不确定您是否需要模板专业化。
希望这会有所帮助:)