我正在阅读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;
}
答案 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 { };
请注意,与Apple
和Orange
不同,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!
在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,您有AbstractVisitor
和KnockingVisitor
。您可以使用Base*
向量并在每个向量上调用accept
,调用visit
,然后KnockingVisitor
执行敲门操作。使用标签调度,您可以使用标签。这可能是OOP技术和标签调度之间的蹩脚比较,但它只是众多可能的例子中的一个。与OOP设计一样,在哪里以及使用什么模式,使用C ++模板技术,您需要经验。
这个例子非常原始,因为它背后没有实际的任务是设计,但是,当你有一个更有趣和复杂的雄心勃勃的目标。 C ++模板可能是存档它的选择之一。