C++:从模板参数继承类

Posted

技术标签:

【中文标题】C++:从模板参数继承类【英文标题】:C++: Inherit class from template parameter 【发布时间】:2014-07-15 03:29:42 【问题描述】:

最近看到下面的C++代码-sn-p

template <class B>
class A : public B

   ...
;

我想知道在哪种情况下这样的设计是好的做法?

我的理解是,将超类作为模板参数允许A的用户在实例化A的对象时选择超类。

但如果是这种情况,为所有用作模板参数的类 (B) 使用一个公共超类 C 不是更好吗?并让 A 扩展 C

【问题讨论】:

通常情况相反。但是您显示的模式可用于策略或接口注入。 @πάντα ῥεῖ:设计模式与具体用例无关。 @user695652 不应该,如果它确实关闭,你有我的重新开始投票。 @πάνταῥεῖ:设计模式没有动机部分。关于它们的书籍或文章有,但没有。你是在暗示每一个缺乏例子的抽象问题都太宽泛了吗?实际上,我有时会收到错误我的问题的答案因为我添加了示例;即我得到的答案是针对我的示例而不是问题,因此有时跳过示例实际上更好。 确定,我撤回了我的近距离投票。我渴望阅读复杂的答案;) 【参考方案1】:

常用于实现static polymorphism。

用例是:

Policy-based design Curiously recurring template pattern Barton–Nackman trick

一般来说,您可以从动态多态中获益,而无需额外的虚函数运行时成本。但只有在编译时可以确定具体类型时才有用。

【讨论】:

可能值得注意的是,CRTP 与 OP 的示例显示相反。【参考方案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;

例如,包装类可以提供对派生类的同步访问。

【讨论】:

既继承又拥有相同类型的成员似乎有点浪费,为什么不直接调用父成员函数T::f()【参考方案3】:

它经常用于所谓的“基于策略”的设计,即通过与所需的派生类组合来向基类添加特性,请参阅 Andrei Alexandrescu 的“现代 C++ 设计:应用的通用编程和设计模式”。您从中派生的实例化模板类称为“策略”。这种设计有时比继承更好,因为它允许组合策略并避免基于继承的模型中不可避免的组合爆炸。

例如看下面的简单代码,其中REDBLUE 是为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

【讨论】:

好点,但你的例子并不能证明继承是合理的。可能是作曲。 @erenon 这是一个简单的例子,但在更现实的场景中,您希望编写基类以便它适用于任何策略,这就是为什么(在我的理解中)您从模板类继承.否则,您将不得不专门继承所需的策略,即为每个可能的策略组合创建一个不同的类。使用模板,您定义了一个(模板化的)基类,代码变得更加优雅且更易于维护(例如,可以从 Base 模板类继承,否则您必须从每个可能的策略组合继承)。 【参考方案4】:

我使用这种风格的地方是我需要实现一个既易于使用又易于维护的通用图形库 过了一会儿,我有了这个设计:

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 列表类看起来像这样:

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

【讨论】:

以上是关于C++:从模板参数继承类的主要内容,如果未能解决你的问题,请参考以下文章

从可变参数模板中扩展的 decltype 继承

如何为从C++中的模板继承的类重载赋值运算符

C++:类模板遇到继承

C++模板类的继承

c++ 泛型 模板继承问题

C++ 提高教程 模板-类模板雨继承