如何在c++中使用泛型编程来代替多态性

How can generic programming be used instead of polymorphism in C++?

本文关键字:泛型编程 多态性 c++      更新时间:2023-10-16

我被告知可以使用泛型编程(在c++中)来代替多态,但我从来没有理解如何/为什么。当我的类可以使用多种类型时,我总是使用泛型编程,当我想要与派生类不同的行为(即标准的多态行为)时,我使用多态编程。我只是不明白这两个(看似)不同的概念之间的联系。我在想,你可以使用一个多态函数来显示基于用户输入的数据类型的不同行为,但我甚至不确定这是否可能。

如何用泛型编程代替多态编程?这样做的好处是什么?如果可能,请提供一个例子。

在c++中,有两种类型的多态性:编译时和运行时

编译时多态性

编译时多态性是通过使用模板实现的。依赖于这种形式的多态性也被称为泛型编程。

标准库中的许多函数,如std::sort, std::find,都依赖于编译时多态性。只要用于实例化函数的参数支持所需的功能,这些函数就可以正常工作。

简单的例子:

template <typename T>
T const& min(T const& lhs, T const& rhs)
{
   return (lhs < rhs) ? lhs : rhs;
}

min的实现将能够返回两个值的min,无论它们的类型如何,只要该类型支持lhs < rhs的操作。

运行时多态性

运行时多态性是通过使用虚函数来实现的。这是更广为人知的多态性形式。

依赖运行时多态性的函数使用基类指针或引用。它们调用在基类级别声明的虚函数。调用根据与对象关联的运行时信息分派给派生类。

简单的例子:

struct Shape
{
   virtual double getArea() const = 0;
}
void foo(Shape const& s)
{
    double area = s.getArea();
    // Use area
}

foo将值任何有效的Shape对象,而不管具体类型。

派生类多种类型。每个派生类都是一个类型,因此拥有不同的派生类意味着拥有多个类型。

泛型通常优于运行时多态性,因为类型信息不会丢失——编译器仍然具有访问权限,因此它可以支持更大程度的类型检查。运行时多态性在这方面失败的一个简单例子是非泛型集合类型。

你需要明确你想要达到的目标;你的第二个问题应该是怎样的。您可以使用多态性来避免编写新代码,也可以使用它来避免更改旧代码。

如果您想要的只是一个类或函数对不同类型的行为不同,那么静态多态就可以达到目的。有时简单的函数重载就足够了;有时你必须使用模板;有时,您将不得不专门为边缘情况定制模板。这主要解决了编写新的冗余代码的问题:如果编译器可以为你编写IntVectors和StringVectors,为什么还要编写它们呢?

只有当您想通过基类指针引用类时,才应该派生类。动态多态性允许您重用现有代码而无需修改它:如果您有一个在屏幕上显示小部件的函数,则在引入新类型的小部件时不需要重写它。

现在,如果您可以将静态多态性和动态多态性结合起来,您就拥有了这种能力。随机想到的一个例子是电子表格,但它可能不是最好的:

class BaseCell {
    // ...
public:
    virtual void draw() = 0;
};
template<class CellFormat>
class Cell : public BaseCell {
public:
    virtual void draw(); // uses something supplied by CellFormat
};
// somewhere else
vector<shared_ptr<BaseCell>> cells {
    shared_ptr<BaseCell>{new Cell<IntFormat>()},
    shared_ptr<BaseCell>{new Cell<CurrencyFormat>()}
};
for (auto cell: cells) {
    cell->draw();
}

通过这种方式,您可以编写使用BaseCell提供的接口的未来证明函数,减少对现有代码库的更改,并为新支持的格式生成不同的类,减少必须编写的新代码。

总而言之:运行时和静态多态性不是替代品,而是互补的技术,它们共享不同行为的统一接口的思想。