С++: наследовать класс из параметра шаблона

Недавно я увидел следующий фрагмент кода на С++

template <class B>
class A : public B
{
   ...
};

и мне интересно, в какой настройке такой дизайн является хорошей практикой?

То, как я это понимаю, заключается в том, что наличие суперкласса в качестве параметра шаблона позволяет пользователям A выбирать суперкласс при создании экземпляра объекта A.

Но если это так, не лучше ли иметь общий суперкласс C для всех классов (B), которые используются в качестве аргумента шаблона и A расширяем C?

Ответ 1

Часто используется для реализации статического полиморфизма.

Случаи использования:

В целом вы получаете преимущества от динамического полиморфизма, без дополнительных затрат времени на виртуальные функции. Но это полезно, только если конкретный тип можно определить во время компиляции.

Ответ 2

Звучит как хороший кандидат для класса-оболочки:

class base {
public:
  virtual void f() = 0;
};

class d1 : public base {
public:
  virtual void f() override { ... };
};

class d2 : public base {
public:
  virtual void f() override { ... };
};

template <typename T>
class wrapper : public T {
public:
  virtual void f() override {
    pre_op();
    T::f();
    post_op();
  }

private:
  void pre_op() { ... }
  void post_op() { ... }
}

int main(int, char**) {
  wrapper<d1> w1;
}

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

Ответ 3

Он часто используется в так называемом "основанном на политике" дизайне, т.е. вы добавляете характеристики в базовый класс по композиции с требуемыми производными классами, см. "Современный дизайн С++: общие шаблоны программирования и дизайна", применяемые Андреем Александреску. Созданный экземпляр класса шаблонов, из которого вы получаете, называется "политикой". Такой дизайн иногда лучше, чем наследование, поскольку он позволяет сочетать политики и избегать комбинаторного взрыва, неизбежного в модели наследования.

См., например, следующий простой код, где RED и BLUE составляют политики для Pen:

#include <iostream>
#include <string>

struct RED
{
    std::string getColor()
    {
        return "RED";
    }
};

struct BLUE
{
    std::string getColor()
    {
        return "BLUE";
    }
};

template <typename PolicyClass>
class Pencil: public PolicyClass
{
public:
    void Draw()
    {
        std::cout << "I draw with the color " << PolicyClass::getColor() << std::endl; 
    }
};


int main()
{   
    Pencil<RED> red_pencil; // Drawing with RED
    red_pencil.Draw();
    Pencil<BLUE> blue_pencil; // Different behaviour now
    blue_pencil.Draw();

    return 0;
}

Можно немного прочитать здесь: http://en.wikipedia.org/wiki/Policy-based_design

Ответ 4

Место, где я использую этот стиль, было там, где мне нужно реализовать общую библиотеку графов, которая проста и удобна в обслуживании Через некоторое время я пришел с этим дизайном:

Класс ABstract для GraphContainer, Edge, Node:

template < class T1,class T2>
class  GraphAbstractContainer
{
public:
    using Node = T1;
    using Edge = T2;
    virtual std::list<Node> getConnectedNodes(const Node& node)const = 0;
    virtual Node addNode(const Node&) = 0;
    //...
};

class  GraphAbstracthNode
{
public:
    virtual uint32_t getId() const = 0;
    virtual void setID(uint32_t id)=0;
    //..
};

template<class T>
class  GraphAbstractEdge
{
public:
    using Node = T;
    //GraphAbstractEdge(){}
    virtual Node  firstNode() const = 0;
    virtual Node   secondNode() const = 0;
    virtual void  setFirstNode(const Node& node)  = 0;
    virtual void  setSecondNode(const Node& node) = 0;
    //...

};

Затем я добавляю реализацию Adj_List и Adj Matrix с помощью наследования непосредственно из параметров шаблона.

например, класс классного класса Adj List выглядит примерно так:

template<class T1 = GraphAbstractContainer<GraphAdjNode,
                   GraphAdjEdge>>
class  GraphAdjListContainer : public T1
{
public:
    using Node = typename T1::Node;
    using Edge = typename T1::Edge;

    //return connected Nodes
    virtual std::list<Node> getConnectedNodes(const Node& node) const
    {
        //..
    }
    //..
  };

};

template<class T>
class  GraphAdjNode : public T
{
public:
    //implementing abstract class methods...
};

template<class T>
class  GraphAdjEdge : public T
{
public:
   //...

};

И также класс My Graph наследует непосредственно из шаблона:

template<class GraphContainer=GraphAdjListContainer<>>
    class   Graph :public  GraphContainer
    {
    public:
        using Node = typename GraphContainer::Node;
        using Edge = typename GraphContainer::Edge;
         //...

}

Преимущество этого шаблона проектирования - это просто изменить весь класс, лежащий в основе, просто наследовать от абстрактных классов и заполнить параметры шаблона.

например, я определяю структуру данных Trie, просто делая это:

class TrieNode :public GraphAdjNode
{
public:
    //...
    std::string word_;
};

class Trie 
{
public:
    using Graph = Graph < ecv::GraphAdjListContainer<TrieNode, ecv::GraphAdjListEdge<TrieNode>>>;
    using Node =  Graph::Node;
    using Edge =  Graph::Edge;
    void addWord(wstring word);
    //...
private:
    Graph graph_;
}