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

13 c++ templates overriding runtime static-polymorphism

这是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;
}
Run Code Online (Sandbox Code Playgroud)

我已经读过C++中的模板是一个编译时功能,它不像C#/ Java中的泛型.

据我所知,C++编译器会将单个定义的函数划分为不同的数字(取决于具有不同类型的调用计数)的函数.

我是对还是不对?我不是C++编译器的专家,所以我向你提出一些建议.

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

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

Ton*_*roy 13

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>()
Run Code Online (Sandbox Code Playgroud)

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


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

并不是的.

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

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

    • 平凡,这样的实例中typeid(T)被用于评估intdouble有效的行为从程序员的角度多态(这是一个编译器关键字虽然-实现未知)

  • 平凡的,静态和名义上的动态混合(但这里可能是可以优化的静态)多态性支持你使用 std::cout

背景 - 多态和代码生成

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

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

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

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

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

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

    • 也就是说,没有包含if (type == X) f1(x) else f2(x);样式代码的程序源代码
  • 减少维护负担,因为在明确更改变量类型后,需要在整个源代码中手动进行更少的后续更改

C++支持这些更大的图片方面如下:

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

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

...根据我对c ++中的多态性的回答,并以一些小的方式

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

  • dispatch(2)可以在实例化(1)期间发生模板和预处理器,

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

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

你的代码实际使用了什么?

function<int>function<double>重复使用的function模板的代码来创建针对每个这些类型的不同的码,所以你正在越来越实例(1)如上述.但是,您正在硬编码调用哪个实例化而不是让编译器根据某个参数的类型隐式选择实例化,即因此您在调用时直接使用隐式调度ala(2)function.实际上,function缺少编译器可用于隐式选择模板实例化的参数.

实例化(1)单独是没有足够考虑您的代码有使用多态.不过,您已经实现了方便的代码重用.

那么什么是明确的多态?

为了说明模板如何支持dispatch(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>(...)
Run Code Online (Sandbox Code Playgroud)

上面的代码还利用隐式调度来键入类型 - 方面"2".以上 - 多态性.


非类型参数

有趣的是,C++提供了使用整数参数(如布尔值int和指针常量)实例化模板的能力,并且可以在不改变数据类型的情况下将它们用于各种方式,因此不涉及任何多态性.宏更灵活.


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


关于术语的讨论

获得多态性的明确定义很困难.维基百科引用Bjarne Stroustrup的在线术语表"为不同类型的实体提供单一接口":这意味着struct X { void f(); }; struct Y { void f(); };已经表现出多态性,但是当我们使用来自客户端代码的接口的对应关系时,我们只获得多态性,例如template <typename T> void poly(T& t) { t.f(); },t.f()对于每个实例化需要静态多态分派.


小智 5

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

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

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

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

第一个是指函数重载.第三种类型是指后期绑定或运行时多态,您可以在继承中看到这种类型.第二个是我们感兴趣的.

模板是编译时构造,类型推导是编译器自动计算模板参数的过程.这就是静态多态性的来源.

例如:

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

这适用于兼容plus运算符的任何两种类型.如果编译器可以解决问题,则无需指定模板参数.如果您编写了执行不同行为的函数重载,例如字符串连接与整数加法,那么它将是ad hoc多态.

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

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

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