Is partial class template specialization the answer to this design problem?

前端 未结 4 1256
被撕碎了的回忆
被撕碎了的回忆 2020-12-30 15:12

Say you have a class who\'s job it is to connect to a remote server. I want to abstract this class to provide two versions, one that connects through UDP and the other throu

相关标签:
4条回答
  • 2020-12-30 15:17

    I think that the main point in choosing amongst polimorphism or template specialization, in this particular case at least, is if you want to choose which behavior to use at run time or at compile time.
    If you want to have a udp or a tcp connection based, for example, on a connection string provided the user, then polimorphism best fits your needs; create a concrete class and then pass it to generic code that handles a pointer to a base interface.
    Otherwise, you might consider using templates - I'm not sure if you need template specialization.

    Hope this helps :)

    0 讨论(0)
  • 2020-12-30 15:28

    Templates are not necessary (though a possible solution). This is just dependency injection via templates rather than via a constructor. Personally I would do it via a constructor. But doing it via template gives you the dubious benifit of a cheaper method call (it does not need to be virtual). But also does allow for easier compiler optimization.

    Both the udp and tcp objects must still support the same interface.
    If you do it via inheritance they must both implement a common interface (virtual base class), it it is done via templates this is not necessary but the compiler will check that they support the same method calls that the Service object requires.

    As asked in the original question, I see no explicit need(or benefit) for partial template specialization (in the situation as described).

    Template Method

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

    Polymorphic Version

    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 讨论(0)
  • 2020-12-30 15:34

    I would use the curious recuring template pattern, aka 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(...) {};
    };
    

    There would probably be many more template parameters and sub classes but you get the idea, you can also use static methods.

    By the way template code is generally more efficient but also much bigger.

    0 讨论(0)
  • 2020-12-30 15:41

    This can be best done using a policy for the transport protocol:

    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;
    

    Note that this is also polymorphic. It's called compile time polymorphism. Putting the policy into a base class will benefit from the Empty-Base-Class-Optimization. That is, your base class does not need to take any space. Putting the policy as a member has the other drawback that you always have to delegate stuff to that member, which can become annoying with time. The book Modern C++ Design describes this pattern in-depth.

    Ideally, the transport protocol doesn't need to know anything about the protocol above it. But if for some reason you have to get some information about it, you can use the crtp pattern 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;
    

    You don't have to put your templates into headers. If you explicitly instantiate them, you will gain faster compilation times, as much fewer code has to be included. Put this into service.cpp:

    template class service<tcp>;
    template class service<udp>;
    

    Now, code that uses service does not need to know about the template code of service, since that code is already generated into the object file of service.cpp.

    0 讨论(0)
提交回复
热议问题