C++ 中是不是存在真正的静态多态性?
Posted
技术标签:
【中文标题】C++ 中是不是存在真正的静态多态性?【英文标题】:Is there real static polymorphism in C++?C++ 中是否存在真正的静态多态性? 【发布时间】:2013-12-26 08:20:03 【问题描述】:这是一个简单的 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++ 代码级别,我想t 看看编译器是如何产生输出的。
【问题讨论】:
@JoeZ 为什么不是静态多态性? @jalf :我脑子里有一个错误的想法。我会删除我的评论。 【参考方案1】: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
背景 - 多态性和代码生成
我认为对多态性至关重要的要求是:
当代码被编译时(无论是“普通”代码还是每个模板实例化或宏替换),编译器会自动选择(必要时创建)——内联或调用——不同的适合类型的行为(机器码)
即代码选择/创建由编译器仅基于所涉及的变量的类型来完成,而不是通过程序员在不同的函数名称/实例之间的选择进行显式硬编码,每个函数名称/实例只能处理一种类型或排列类型
1234563如果x
变为char
,则手动修改为printf("%c", x);
。
但是,我们试图通过多态来实现的目标更加通用:
在不嵌入显式类型检测和分支代码的情况下对多种类型的数据重复使用算法代码
即没有包含if (type == X) f1(x) else f2(x);
风格代码的程序源代码
减少了维护负担,因为在显式更改变量类型后,需要在整个源代码中手动进行的后续更改更少
C++ 支持的这些宏观方面如下:
实例化相同的源代码以生成不同的行为(机器代码)用于某些其他类型或类型的排列(这是的一个方面参数化 多态),
实际上称为模板的“实例化”和预处理器宏的“替换”,但为了方便起见,我将在下文中使用“实例化”;从概念上讲,重新编译或重新解释......隐式调度(静态或动态)到不同的行为(机器代码)适合正在处理的不同类型的数据。
...根据我在Polymorphism in c++ 的回答,在一些小方面
不同类型的多态性涉及其中一个或两个:
dispatch (2) 可能发生在实例化过程中 (1) 对于模板和预处理器宏 ,
实例化(1)通常发生在调度期间(2)模板(没有匹配的完全专业化)和功能-比如宏(有点循环,虽然宏不会递归扩展)
dispatch (2) 可以发生 without 实例化 (1) 当编译器选择预先存在的函数 overload 或 模板专业化,或者当编译器触发 virtual/dynamic dispatch。
你的代码实际使用了什么?
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
和指针常量)实例化模板的能力,并将它们用于各种事物,而无需改变数据类型,因此不涉及任何多态性。宏更加灵活。
请注意,在 C.R.T.P. 中使用模板。 style NOT 是静态多态性的要求——它是一个示例应用程序。在实例化期间,编译器在将操作与参数指定类型中的实现进行匹配时表现出静态多态性。
术语讨论
很难获得多态性的明确定义。 wikipedia 引用 Bjarne Stroustrup 的在线词汇表“为不同类型的实体提供单一接口”:这意味着 struct X void f(); ; struct Y void f(); ;
已经表现出多态性,但恕我直言,当我们使用来自客户端代码的接口对应关系时,我们才会得到多态性,例如template <typename T> void poly(T& t) t.f();
需要为每个实例化静态多态分派到 t.f()
。
【讨论】:
【参考方案2】:***列出了三种类型的多态性:
如果一个函数表示不同的和潜在的异构实现,具体取决于单独指定的有限范围 类型和组合,称为ad hoc polymorphism。特别指定 使用函数的许多语言都支持多态性 重载。
如果编写代码时没有提及任何特定类型,因此可以透明地与任意数量的新类型一起使用,它是 称为参数多态。在面向对象编程中 社区,这通常被称为泛型或泛型编程。在 函数式编程社区,通常简称为 多态性。
子类型(或包含多态性)是一个概念,其中名称可以表示许多不同类的实例,只要它们是 由一些常见的超类相关。在面向对象编程中, 这通常被简称为多态性。
第一个是指函数重载。第三种类型是指后期绑定或运行时多态性,例如在继承中会看到的那种。第二个是我们感兴趣的。
模板是编译时构造,类型推导是编译器自动计算出模板参数的过程。这就是静态多态性的用武之地。
例如:
template <typename T, typename U>
auto func(const T& t, const U& u) -> decltype(t + u)
return (t + u);
这适用于具有兼容加号运算符的任何两种类型。如果编译器可以弄清楚,则无需指定模板参数。如果您编写执行不同行为的函数重载(例如字符串连接与整数加法),那将是临时多态性。
但是,在您的示例中,您的函数具有不同的实例化,function<int>
和 function<double>
。这是一个报价:
要成为多态,[a()] 必须能够使用 at 的值进行操作 至少两种不同的类型(例如 int 和 double),查找和执行 适合类型的代码。
在这种情况下,实例化特定于它们被实例化的类型,因此不涉及多态性。
【讨论】:
【参考方案3】:您的示例中没有静态多态性,因为没有多态性。这是因为function<int>()
看起来与function<double>()
不同。
静态多态的示例包括简单的函数重载、可以使用类型推导的函数模板、类型特征和curiously recurring template pattern (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
【讨论】:
而且,谈到 C++ 的未来,对于使用concepts
的某种编译类型多态性有一点希望,这可能会成为下一个 C++14 标准的重要组成部分.
我没有“否决”任何事情。我有一个建议,也许你可以在你的答案中提到类型特征,一个没有concepts
的 C++ 的穷人解决方案,但我认为你的帖子没有什么不正确的地方。
@user2485710 我在示例列表中添加了类型特征。这并不是一个详尽的清单,但它是一个很好的例子。
这里没有类型特征...和rtti不一样。
哦,我误读并假设您的意思是代码示例。没关系:-)【参考方案4】:
对于 c++,术语“静态多态性”通常用于例如CRTP 类型设计模式:
template<typename Derived>
class Base
void someFunc()
static_cast<Derived*>(this)->someOtherFunc();
;
;
class ADerived : public Base<ADerived>
void someOtherFunc()
// ...
;
一般意味着在编译/链接时推导和验证类型和继承约束。如果指定类型上的操作丢失或无效,编译器将发出错误消息。从这个意义上说,它并不是真正的多态性。
【讨论】:
【参考方案5】:虽然可以说 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; int
和 double
的专用(派生)实例可以具有完全不同的行为,在简单的情况下通过不同的返回值证明,对于具有例如 int 参数的模板也可以这样做,并且可以展示可以奇怪地称为静态递归多态性。
虽然polymorphic
一词可能被延伸,但这个概念肯定存在。缺少的不是重新定义函数的能力,而是专用类自动继承不改变行为的函数的能力。
【讨论】:
以上是关于C++ 中是不是存在真正的静态多态性?的主要内容,如果未能解决你的问题,请参考以下文章