я читал SEQAN документация. В разделе «Приступая к работе» они упомянули «Создание подклассов шаблонов» как метод для преодоления накладных расходов на полиморфизм во время выполнения.
ООП против общего программирования:
В SeqAn мы используем технику, называемую подклассами шаблонов, которая основана на обобщенном программировании. Этот метод обеспечивает полиморфизм в программах на C ++ во время компиляции с использованием шаблонов. Такой статический полиморфизм отличается от полиморфизма во время выполнения, который поддерживается в C ++ с использованием подклассов и виртуальных функций. Это происходит за счет некоторой дополнительной типизации, но имеет то преимущество, что компилятор может встроить все вызовы функций и, таким образом, достичь лучшей производительности. Пример будет приведен в разделе «От ООП до 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;
}
После краткого ознакомления с руководством по 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, перемещается из области обычного программирования ООП (с полиморфизмом, абстракциями типов и т. Д.) В программирование на C ++ (более точно см. Пункт 1 в Эффективный C ++). Так же, как есть множество книг по ООП-дизайну, не меньше, если не больше, есть материал по программированию на С ++ (см. Полное руководство и список книг C ++ для обоих видов техники программирования и многое другое).
В этом примере я показываю два метода проектирования, которые подчеркивает документация SeqAn.
Существует база, реализующая какую-то операцию, которая имеет несколько общих шагов для других подклассов. База обеспечивает вид Шаблонный метод (там нет ничего, связанного с шаблонами C ++, это просто шаблон ООП, просто имя имеет одно и то же слово) из операций — установка интерфейса (не интерфейса ООП, а интерфейса шаблона C ++) для производных классов. Эти подклассы, в свою очередь, реализуют эти операции. Ты получаешь статический (решено во время компиляции поведения) полиморфизм (тип, у которого есть интерфейс, который имеет различное поведение от одного экземпляра до другого). Полиморфизма во время выполнения не существует, потому что для среды выполнения (области ООП) все они являются разными типами, они также различаются и во время компиляции. Этот полиморфизм существует в шаблонах C ++ (параметры шаблонов и объявления шаблонов). Пример: Boost Iterator Facade.
Диспетчерская метка В этом методе используется разрешение перегрузки функций в зависимости от типа (ов) аргумента (ов). Это помогает, когда у вас есть свободная функция как часть вашего API и вы выбрали необходимую для вызова в зависимости от ситуации (пример STL Iterators iterator_tag
(s) и функции без итераторов). Например, рассмотрим ООП Шаблон посетителя и у тебя есть AbstractVisitor
а также KnockingVisitor
, Вы берете вектор Base*
и позвонить accept
на каждом, что вызывает visit
и ваш KnockingVisitor
стучит. С диспетчеризацией тегов вы используете теги. Это может быть слабое сравнение между методами ООП и диспетчеризацией тегов, но это только один из многих возможных примеров. Как и в ООП-дизайне, где и какой шаблон использовать, с технологиями C ++ Templates вам нужен опыт.
Этот пример довольно примитивен, потому что за ним нет реальной задачи — это дизайн, однако, когда у вас есть амбициозная цель, которая становится более интересной и сложной. Шаблоны C ++ могут быть одним из вариантов архивирования.
Существует существующий вопрос / ответ, который описывает, что означает подклассы шаблонов и как они используются в 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
метод для Apple
s:
template <>
inline void eat(Fruit<Apple> const&) {
std::cout << "scrunching an apple\n";
}
Так как мы не переопределяем метод для Orange
s, они всегда будут вызывать метод базового класса.
Вот еще несколько специализаций для цитрусовых (иерархия подклассов):
struct Lemon;
struct Lime;
template <typename Spec = Lemon> struct Citrus { };
Обратите внимание, что, в отличие от Apple
с и Orange
s, 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
на самом деле в этом нет необходимости, и вместо специализации шаблонов здесь будет использоваться диспетчеризация + перегрузка (как упоминалось Юки).
Также обратите внимание, что мой код не использует никакого фактического наследования на языке C ++ (т.е. Fruit<Apple>
не наследуется от Fruit<whatever>
). Это не является строго необходимым, но часто весьма полезным, и большинство шаблонов классов SeqAn IIRC делать также фактически наследуют их базовый класс.