0

我正在阅读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;
}
4

2 回答 2

3

有一个现有的问题/答案描述了模板子类化的含义,以及它在 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";
}

请注意,模板子类化中的方法始终是自由函数,因为方法分派严格通过重载而不是像传统子类化中的覆盖来工作。

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

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

struct Orange;
struct Apple;

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

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

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

由于我们不覆盖Oranges 的方法,因此它们将始终调用基类方法。

这里有一些更专业的柑橘类水果(一个子类层次结构):

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

请注意,与 for Apples 和Oranges 不同,Citrus它本身是一个可以子类化的模板。eat我们现在可以像以前一样覆盖Citrus水果,但我想展示子类方法如何分派给基类方法;为此,让我们引入一个辅助函数模板eat_citrus,它将从以下位置调用eat

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

eat_citrus这是for any的基类定义Citrus

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

这是Limes 的覆盖:

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 类模板实际上也继承了它们的基类

于 2017-09-29T12:03:33.363 回答
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++ 模板编程(更准确地参见有效 C++中的第 1 条)。就像有大量关于 OOP 设计的书籍一样,有很多关于模板 C++ 编程的资料(参见The Definitive C++ Book Guide and List了解两种编程技术等等)。

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

  • 有一个实现某种操作的基础,该操作具有其他子类共有的几个步骤。基础提供了一种模板方法(那里没有与C++模板相关的东西,它是纯粹的OOP模式,只是名称有相同的词)来自操作 - 为派生设置一个接口(不是OOP接口,而是C++模板接口)类。这些子类依次实现这些操作。你得到静态(在编译时行为解决)多态性(具有从一个实例到另一个实例具有不同行为的接口的类型)。没有运行时多态性,因为对于运行时(OOP 领域)来说,它们都是不同的类型,它们在编译时也是不同的。这种多态性存在于 C++ 模板(模板参数和模板声明)中。示例:Boost 迭代器外观

  • 标签调度技术根据参数类型采用函数重载决议。当您将自由函数作为 API 的一部分并根据情况选择必要的函数(例如STL 迭代器 iterator_tag和迭代器自由函数)时,它会有所帮助。例如,考虑 OOP访问者模式AbstractVisitor,并且您拥有KnockingVisitor. 您获取一个向量Base*并调用accept每个向量,该向量调用visit并且您KnockingVisitor进行敲门。通过标签调度,您可以使用标签。这可能是 OOP 技术和标签调度之间的一个蹩脚的比较,但这只是众多可能的例子之一。就像 OOP 设计在哪里使用什么模式一样,对于 C++ 模板技术,您需要经验。

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

于 2017-06-08T15:59:18.780 回答