8

假设您有一个班级,其工作是连接到远程服务器。我想抽象这个类来提供两个版本,一个通过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

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

我会使用奇怪的重复模板模式,也就是五点手掌爆炸 Alexandrescu 技术:

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 对象都必须仍然支持相同的接口
如果您通过继承来实现,它们都必须实现一个通用接口(虚拟基类),它是通过模板完成的,这不是必需的,但编译器将检查它们是否支持服务对象所需的相同方法调用。

正如原始问题中所问的那样,我认为部分模板专业化没有明确的需要(或好处)(在所描述的情况下)。

模板法

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 回答