13

下面是一个简单的 C++ 代码:

#include <iostream>
#include <typeinfo>

template<typename T>
void function()
{
   std::cout << typeid(T).name() << std::endl;
}

int main()
{
   function<int>();
   function<double>();
   return 0;
}

我读过 C++ 中的模板是一个编译时特性,它不像 C#/Java 中的泛型。

据我了解,C ++编译器会将单个定义的函数划分为各种数量(取决于不同类型的调用计数)的函数。

我是对还是错?我不是 C++ 编译器方面的专家,所以我向您请教一条建议。

如果我对编译器输出的建议是正确的,我想知道我是否可以将上面的代码描述为静态多态性

因为它似乎没有覆盖,而只是从可执行文件中调用一个副本,或者......应用程序在输出二进制图像中有什么并不重要,但只有重要的部分是在 C++ 代码级别,我不必看编译器如何产生输出。

4

5 回答 5

15

C++ 中是否存在真正的静态多态性?

绝对 - 静态多态性有三种机制:模板、宏和函数重载。

据我了解,C++ 编译器会将单个定义的函数划分为不同数量(取决于不同类型的调用计数)的函数。我是对还是错?

这是一般的想法。被实例化的函数的数量取决于模板参数的排列数量,可以显式指定为function<int>function<double>或 - 对于使用模板参数匹配函数参数的模板 - 自动从函数参数派生,例如:

template <typename T, size_t N>
void f(T (&array)[N])
{ }

double d[2];
f(d);   // instantiates/uses f<double, 2>()

您应该在可执行二进制映像中得到每个实例化模板的单个副本。


我想知道我是否可以将上面的代码描述为静态多态性?

并不真地。

  • template<> function为两种类型实例化

    • 至关重要的是,多态性用于选择function在呼叫站点分派到的两个实例中的哪一个

    • 琐碎地,在这样的实例化过程中,从程序员的角度typeid(T)评估int有效地表现出多态性(尽管它是一个编译器关键字 - 实现未知)double

  • 简单地说,静态和名义上动态(但这里可能对静态优化)多态性的混合支持您使用std::cout

背景——多态性和代码生成

我认为对多态性至关重要的要求是:

  • 当代码被编译时(无论是“普通”代码还是每个模板实例化或宏替换),编译器都会自动选择(必要时创建) - 并且内联或调用 - 不同的适合类型的行为(机器代码)

    • 即代码选择/创建由编译器仅基于所涉及的变量的类型来完成,而不是通过程序员在不同的函数名称/实例之间的选择来明确地硬编码,每个函数名称/实例只能处理一种类型或类型的排列

    • 例如,std::cout << x;多态调用不同的代码,因为类型x不同但仍然输出x的值,而非多态printf("%d", x)处理ints 但需要手动修改为printf("%c", x);ifx变为 a char

但是,我们试图通过多态来实现的是更通用的一点:

  • 在不嵌入显式类型检测和分支代码的情况下对多种类型的数据重用算法代码

    • 即没有包含if (type == X) f1(x) else f2(x);-style代码的程序源代码
  • 减少了维护负担,因为在显式更改变量的类型之后需要在整个源代码中手动进行更少的后续更改

C++ 支持这些更宏观的方面,如下所示:

  1. 实例化相同的源代码,为其他类型或类型排列生成不同的行为(机器代码)是参数多态性的一个方面),

    • 实际上称为模板的“实例化”和预处理器宏的“替换”,但为了方便起见,我将在下文中使用“实例化”;从概念上讲,重新编译或重新解释......
  2. 隐式分派(静态或动态)到适用于正在处理的不同类型的数据的不同行为(机器代码)。

...并且根据我在 C++ 中的多态性中的回答以一些小的方式

不同类型的多态性涉及其中之一或两者:

  • 分派(2) 可能在模板和预处理器宏的实例化(1)期间发生,

  • 实例化(1)通常发生在模板(没有匹配的完全专业化)和类似函数的(循环的种类,尽管宏不递归扩展)的调度(2)期间

  • 当编译器选择预先存在的函数重载模板专门化,或者编译器触发虚拟/动态调度时,调度(2) 可以在没有 实例化(1) 的情况下发生。

您的代码实际使用什么?

function<int>并重function<double>function模板代码为每种类型创建不同的代码,因此您获得如上所示的实例化(1)。但是,您是在硬编码要调用哪个实例化,而不是让编译器根据某个参数的类型隐式选择一个实例化,即在调用时直接使用隐式调度 ala (2) function。实际上,function缺少编译器可用于隐式选择模板实例化的参数。

仅实例化 (1)不足以认为您的代码使用了多态性。尽管如此,您还是实现了方便的代码重用

那么什么是明确的多态性呢?

为了说明模板如何支持分派 (2) 以及实例化 (1) 并无可争辩地提供“多态性”,请考虑:

template<typename T>
void function(T t)
{
    std::cout << typeid(T).name() << std::endl;
}

function(4);      // note: int argument, use function<int>(...)
function(12.3);   // note: double argument, use function<double>(...)

上面的代码还利用隐式分派到类型适当的代码- 方面“2”。以上 - 多态性。


非类型参数

有趣的是,C++ 提供了使用整数参数(如布尔值int和指针常量)实例化模板的能力,并将它们用于各种事物,而无需改变数据类型,因此不涉及任何多态性。宏更加灵活。


请注意,在 CRTP 样式中使用模板不是静态多态性的要求 - 这是其示例应用程序。在实例化期间,编译器在将操作与参数指定类型的实现匹配时表现出静态多态性。


术语讨论

很难获得多态性的明确定义。wikipedia 引用 Bjarne Stroustrup 的在线词汇表“为不同类型的实体提供单一接口”:这意味着struct X { void f(); }; struct Y { void f(); };已经表现出多态性,但是恕我直言,当我们使用来自客户端代码的接口对应关系时,我们只会得到多态性,例如,对于每个实例化都template <typename T> void poly(T& t) { t.f(); }需要静态多态调度t.f().

于 2013-12-26T09:29:05.130 回答
5

维基百科列出了三种类型的多态性:

  • 如果一个函数根据有限范围的单独指定的类型和组合表示不同的和潜在的异构实现,则称为ad hoc polymorphism。使用函数重载的许多语言都支持 Ad hoc 多态性。

  • 如果代码在编写时没有提及任何特定类型,因此可以透明地与任意数量的新类型一起使用,则称为参数多态。在面向对象的编程社区中,这通常被称为泛型或泛型编程。在函数式编程社区中,这通常简称为多态。

  • 子类型(或包含多态性)是一个概念,其中一个名称可以表示许多不同类的实例,只要它们与某个公共超类相关。在面向对象编程中,这通常被简称为多态性。

第一个是指函数重载。第三种类型是指后期绑定或运行时多态性,例如在继承中会看到的那种。第二个是我们感兴趣的。

模板是编译时构造,类型推导是编译器自动计算出模板参数的过程。这就是静态多态性的用武之地。

例如:

template <typename T, typename U>
auto func(const T& t, const U& u) -> decltype(t + u)
{
   return (t + u);
}

这适用于具有兼容加号运算符的任何两种类型。如果编译器可以弄清楚,则无需指定模板参数。如果您编写执行不同行为的函数重载(例如字符串连接与整数加法),那将是临时多态性。

但是,在您的示例中,您的函数具有不同的实例化,function<int>并且function<double>. 这是一个报价:

要成为多态,[a()] 必须能够使用至少两种不同类型(例如 int 和 double)的值进行操作,找到并执行适合类型的代码。

在这种情况下,实例化特定于实例化它们的类型,因此不涉及多态性。

于 2013-12-26T08:30:36.903 回答
4

您的示例中没有静态多态性,因为没有多态性。这是因为function<int>()看起来不一样function<double>()

静态多态性的示例包括简单的函数重载、可以使用类型推导的函数模板、类型特征和奇怪的重复模板模式(CRTP)。因此,您示例的这种变体将符合静态多态性:

#include <iostream>
#include <typeinfo>

template<typename T>
void function(T)
{
   std::cout << typeid(T).name() << std::endl;
}

int main()
{
   function(0);   // T is int
   function(0.0); // T is double
   return 0;
}

这是另一个例子:

template<typename T>
void function(T t)
{
  t.foo();
}

struct Foo() 
{
  void foo() const {}
};

struct Bar() 
{
  void foo() const {}
};

int main()
{
  Foo f;
  Bar b;
  function(f); // T is Foo
  function(b); // T is Bar
}
于 2013-12-26T08:51:55.790 回答
2

对于 c++,术语“静态多态性”通常用于例如CRTP类型的设计模式:

template<typename Derived> 
class Base
{
      void someFunc() {
          static_cast<Derived*>(this)->someOtherFunc();
      };
};

class ADerived : public Base<ADerived>
{
    void someOtherFunc() { 
        // ... 
    }
};

它通常意味着在编译/链接时推断和验证类型和继承约束。如果指定类型上的操作丢失或无效,编译器将发出错误消息。从这个意义上说,它并不是真正的多态性。

于 2013-12-26T08:27:44.100 回答
0

虽然可以说 OP 中的示例没有表现出静态多态性,但使用专业化可以使案例更具说服力:

template<class T>
class Base
{
public:
      int a() { return 7; }
};

template<>
class Base<int>
{
public:
      int a() { return 42; }
};

template<>
class Base<double>
{
public:
      int a() { return 121; }
};

我们在这里看到,对于大多数类 a() 将返回 7;专门的(派生的)实例intdouble可以具有完全不同的行为,在简单的情况下通过不同的返回值证明,对于具有例如 int 参数的模板也可以这样做,并且可以表现出奇怪地称为静态递归多态性的东西.

虽然这个词polymorphic可能会被延伸,但这个概念肯定是存在的。缺少的不是重新定义函数的能力,而是专用类自动继承不改变行为的函数的能力。

于 2013-12-26T15:00:19.557 回答