模板子类化

时间:2017-06-08 09:11:54

标签: c++ templates seqan

我正在阅读SEQAN文档。在“入门”部分中,他们提到“模板子类”作为克服运行时多态性开销的方法。

  

OOP与通用编程:   在SeqAn中,我们使用一种称为模板子类的技术,该技术基于通用编程。这种技术使用模板在编译时为C ++程序提供多态性。这种静态多态性与使用子类和虚函数在C ++中支持的运行时多态性不同。它以一些额外的输入为代价,但具有编译器可以内联所有函数调用并因此实现更好性能的优点。一个例子将在“第一步教程”中的“从OOP到SeqAn”一节中给出。

不幸的是,没有例子可以说明它的用法。如果有人提供一个简单的例子,我真的很感激。

我写了简单的模板类,但我不确定这是否是模板子类化的意思!

struct Base {
    virtual void nockNock() {
        std::cout << "I am Base class." << std::endl;
    }
};


template<typename T>
class Derived: public Base {
public:

    void anotherNock() {
        std::cout << "It's me the Derived class." << std::endl;
    }
    void nockNock() {
        std::cout << "I am Derived class." << std::endl;
        anotherNock();
    }

public:
    Derived(){};
};



int main() {
    Base* myArray[10];

    myArray[0] = new Derived<int>;

    myArray[0]->nockNock();

    return 0;
}

2 个答案:

答案 0 :(得分:2)

There’s an existing question/answer that describes what template subclassing means, and how it’s used in SeqAn。让我举一个更简单的例子。

// No default `Spec` given = “virtual” base class!
template <typename Spec> struct Fruit {};

template <typename Spec>
inline void eat(Fruit<Spec> const&) {
    std::cout << "nibbling an unknown fruit\n";
}

请注意,模板子类化中的方法是始终自由函数,因为方法调度严格地通过重载工作,而不是像传统的子类一样重写。

这定义了Fruit的基类,以及一个方法eat,可以在任何水果上调用。

我们现在将定义一个专业化层次结构(“子类”)。首先,简单的;这些只是类型标记,将插入Spec模板参数:

struct Orange;
struct Apple;

就是这样。我们甚至不需要在我们的示例中定义这些标记,声明它们就足够了(但对于更复杂的情况,可能需要定义;在SeqAn中,始终定义类型标记)。

现在,这是对eat s的Apple方法的覆盖:

template <>
inline void eat(Fruit<Apple> const&) {
    std::cout << "scrunching an apple\n";
}

由于我们没有覆盖Orange的方法,因此它们总是调用基类方法。

以下是柑橘类水果(子类层次结构)的一些特殊化:

struct Lemon;
struct Lime;
template <typename Spec = Lemon> struct Citrus { };

请注意,与AppleOrange不同,Citrus本身就是一个可以进行子类化的模板。我们现在可以像以前一样覆盖eat个结果的Citrus,但是我想展示一个子类方法如何分配到基类方法;为此,我们将介绍一个辅助函数模板eat_citrus,它将从eat调用:

template <typename Spec>
inline void eat(Fruit<Citrus<Spec>> const&) {
    eat_citrus<Spec>();
}

以下是eat_citrus的基本类定义Citrus

template <typename Tag = Lemon>
inline void eat_citrus() {
    std::cout << "ew, that’s sour!\n";
}

这是Lime s的覆盖:

template <>
inline void eat_citrus<Lime>() {
    std::cout << "nice taste, but ";
    eat_citrus<>(); // Calls the base class method.
}

最后,如果我们按如下方式使用这些类:

// Does not work, since `Fruit` is “virtual”:
// Fruit<> fruit;
Fruit<Orange> orange;
Fruit<Apple> apple;
Fruit<Citrus<>> lemon;
Fruit<Citrus<Lime>> lime;

eat(orange);
eat(apple);
eat(lemon);
eat(lime);

...我们得到了这个输出:

nibbling an unknown fruit
scrunching an apple
ew, that’s sour!
nice taste, but ew, that’s sour!

Runnable code on Coliru

在SeqAn中,上述情况略有不同;为了简单起见,我在这里改了它,说实话,因为我和SeqAn合作已经好几年了,我不记得细节了。如果我没记错的话,eat_citrus实际上并不是必需的,这里将使用标签调度+重载(如Yuki所述)而不是模板专业化。

另请注意,我的代码在C ++用语中不使用任何实际继承(即Fruit<Apple>不从Fruit<whatever>继承)。这不是绝对必要的,但通常非常有用,IIRC大多数SeqAn类模板实际上也继承了它们的基类。

答案 1 :(得分:1)

在简要介绍SeqAn手册之后,我想出了以下示例:

namespace Tags {
struct Noone {};
struct Someone {};
}

template <typename T, typename U>
class Base {
public:
  using KindOfThing = U;

  // implement algorithm
  bool knock() {
    static_cast<T*>(this)->knockKnock();
    static_cast<T*>(this)->listen();
    static_cast<T*>(this)->knockKnock();
    static_cast<T*>(this)->tellName("Johnny");
    return static_cast<T*>(this)->knockKnock();
  }
};

template <typename T>
class Base<T, Tags::Noone> {
public:
  using KindOfThing = Tags::Noone;

  void work() {
    static_cast<T*>(this)->makeSounds();
    static_cast<T*>(this)->doJob();
  }
};

class WashingMachine : public Base<WashingMachine, Tags::Noone> {
public:
  void makeSounds(){};

  void doJob(){};
};

class Meow : public Base<Meow, Tags::Someone> {
public:
  Meow() : meows(0) {}

  //implement Base interface
  void listen() { std::cout << "..." << std::endl; }

  //implement Base interface
  bool knockKnock() {
    std::cout << "Meow..." << std::endl;
    return meows++ > 3;
  }

  void tellName(std::string const& name) { (void)name; }

  int meows;
};

class WhoIsThere : public Base<WhoIsThere, Tags::Someone> {
public:
  //implement Base interface
  void listen() { std::cout << "<Steps>..." << std::endl; }

  //implement Base interface
  bool knockKnock() {
    std::cout << "WhoIsThere?" << std::endl;
    return isDone;
  }

  void tellName(std::string const& name) {
    isDone = true;
    std::cout << name + " is here))!" << std::endl;
  }

  bool isDone;
};

template <typename T, typename U>
void performKnocking(T&& item, U) {
  std::cout << "......" << std::endl;
  while (!item.knock())
    ;
}

template <typename T>
void performKnocking(T&& item, Tags::Noone) {
  std::cout << "Noone" << std::endl;
}

template <typename... TArgs>
void performKnockingToEveryone(TArgs&&... sequence) {
  int dummy[] = {(performKnocking(sequence, typename TArgs::KindOfThing()), 0)...};
}

int main() {
  performKnockingToEveryone(
    Meow(), WashingMachine(), WhoIsThere(), Meow(), WashingMachine());
  return 0;
}

关键是SeqAn所说的设计正在从通常的OOP编程领域(具有多态性,类型抽象等)转向C ++模板编程(更准确地说,参见Effective C++中的第1项)。就像有关OOP设计的书籍一样,如果不是更多的话,还有关于模板C ++编程的材料(对于这两种编程技术,请参阅The Definitive C++ Book Guide and List等)。

在这个例子中,我展示了SeqAn文档强调的两种设计技巧。

  • 有一个实现某种操作的基础,其中有几个常用于其他子类的步骤。基础提供了Template Method(与C ++模板没有任何关系,它只是名称具有相同的单词的纯OOP模式) - 设置接口(不是OOP接口,而是C ++模板接口)对于派生类。这些子类又实现了这些操作。您获得静态(在编译时行为时解析)多态(具有从一个实例到另一个实例具有不同行为的接口的类型)。没有运行时多态性,因为对于运行时(OOP领域),这些都是不同的类型,它们在编译时也是不同的。这种多态性存在于C ++模板中(模板参数和模板声明)。示例:Boost Iterator Facade

  • 标签调度技术根据参数类型使用函数重载分辨率。当你将自由函数作为API的一部分并根据情况选择必要的函数时(例如STL Iterators iterator_tag(s)和迭代器自由函数),它会有所帮助。例如,考虑OOP Visitor Pattern,您有AbstractVisitorKnockingVisitor。您可以使用Base*向量并在每个向量上调用accept,调用visit,然后KnockingVisitor执行敲门操作。使用标签调度,您可以使用标签。这可能是OOP技术和标签调度之间的蹩脚比较,但它只是众多可能的例子中的一个。与OOP设计一样,在哪里以及使用什么模式,使用C ++模板技术,您需要经验。

这个例子非常原始,因为它背后没有实际的任务是设计,但是,当你有一个更有趣和复杂的雄心勃勃的目标。 C ++模板可能是存档它的选择之一。