多态(Polymorphism)指的是一种相同的形式(名称和操作等)表现出不同行为的概念,多态的概念由Christopher Strachey于1967年[1]定义为两个分支:特设型多态(Ad-hoc polymorphism)和通用型多态(Universal polymorphism),此处的特设仅与通用相对,并非贬义的。特设型多态在之后又被细分为特设强制多态(Ad-hoc coercion polymorphism)及特设重载多态(Ad-hoc overloading polymorphism,有时候也简称为特设多态)两类,通用型多态也被细分为参数多态(Parametric polymorphism)及包含多态(Inclusion polymorphism,又称子类型多态 Subtyping polymorphism)。通常在 OOP 的语境下我们所指的多态都是包含多态,这同样也是 C++ 标准中[2]唯一提到的多态形式。
特设强制多态中的强制指的是隐式转换这样的语义操作,这使得期望某一类型的地方允许出现不同的类型而不会导致错误,提供的值将会转换到期望的类型,在C++中通常的表现为:
1 2 3 |
int a = 1; float b = a; // a 被隐式转换为 float,b 初始化为转换而来的值(1.0f) float c = a + b; // a 被隐式转换为 float 与 b 进行运算 |
特设重载多态中的重载指的是相同的名称(以及操作等,如操作符重载)在提供不同类型的参数之时使用不同的实现,模板的特化同样可以看作特设重载多态,同样我们通过例子来展现此多态形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
int Add(int a, int b) { return a + b; } float Add(float a, float b) { return a + b; } struct Vector2 { float X, Y; }; Vector2 Add(Vector2 a, Vector2 b) { return { a.X + b.X, a.Y + b.Y }; } int a = 1, b = 2; float c = 1.0f, d = 3.0f; Vector2 e{ 1.0f, 2.0f }, f{ 2.0f, 3.0f }; // 使用类型皆为 int 的参数,以名称 Add 调用函数将会匹配到第一个实现 const int result1 = Add(a, b); // 使用类型皆为 float 的参数,以名称 Add 调用函数将会匹配到第二个实现 const float result2 = Add(c, d); // 使用类型皆为 Vector2 的参数,以名称 Add 调用函数将会匹配到第三个实现 const Vector2 result3 = Add(e, f); // 对于不同类型的参数,Add 表现出不同的行为,这体现了特设重载多态 |
参数多态即借由隐式或显式的参数,使得相同名称的实体表现出不同行为,C++ 中的直接体现即是模板,体现此多态性质的模板可能生成一组函数、类、类型别名、变量及概念(Concept),这样的模板被分别称为函数模板、类模板、别名模板、变量模板及概念,在其他同样具有参数多态的语言中对应函数模板及类模板的概念多被称为泛型函数及泛型类,而对于别名模板、变量模板及概念几乎没有其他语言具有类似的概念:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
template <typename T> T Add(T a, T b) { return a + b; } int a = 1, b = 2; float c = 1.0f, d = 3.0f; // 不需要重复的代码便可达到特设重载多态中的例子的效果,但是不能对没有重载 operator+ 的 Vector2 类型做到相同的事 const int result1 = Add(a, b); const float result2 = Add(c, d); template <typename T> struct Vector2 { T X, Y; Vector2(T x, T y) : X{ x }, Y{ y } { } }; // 不需要对容纳 int 和 float 的 Vector2 都写一个单独的类 // 从 C++17 开始可以对一部分类模板不显式指定模板参数也可实例化对应的类模板了 // v1 具有类型 Vector2<int>,v2 具有类型 Vector2<float> Vector2 v1{ 1, 2 }; Vector2 v2{ 1.0f, 2.0f }; |
包含多态是最常被提到的多态形式,也即子类型多态,注意子类型(Subtyping)与继承(Inheritance)并非等价的概念,子类型一般是用于表达接口的兼容性,即当我们说 B 是 A 的子类型之时,我们所说的是对于 A 的操作都可以对 B 进行,继承则倾向于表现实现的重用,即 B 重用 A 的操作来实现自己的操作,则 B 继承自 A [3]。但很不幸地由于太多语言混淆这两个概念,很少有人能正确地区分它们,包含多态在 C++ 及多数其他语言中以继承的方式体现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
struct Human { string Name; int Age; Human(string name, int age) : Name{ move(name) }, Age{ age } { } // 通常对于多态的基类需要公有的虚析构函数,或者保护的非虚析构函数 virtual ~Human() = default; virtual string DescriptSelf() { return "I'm" + Name + ", " + to_string(Age) + " year(s) old."; } }; struct Student : Human { string Class; Student(string name, int age, string class_) : Human{ move(name), age }, Class{ move(class_) } { } string DescriptSelf() override { return Human::DescriptSelf() + " I'm a student from class " + Class + "."; } }; Student a{ "Chikyuu", 24, "Shimokitazawa" }; Human b{ "Case", 10 }; Human* p = &a; // 虽然 p 的静态类型是 Human*,但所指向的对象的动态类型是 Student,因此结果为“I'm Chikyuu, 24 year(s) old. I'm a student from class Shimokitazawa.” p->DescriptSelf(); p = &b; // p 所指向的对象的动态类型是 Human,输出为“I'm Case, 10 year(s) old.” p->DescriptSelf(); |
引用:
[1] C. Strachey, Fundamental concepts in programming languages, Notes for the International Summer School in Computer Programming, Copenhagen (1967)
[2] ISO/IEC 14882:2017 [class.virtual]
[3] https://www.cmi.ac.in/~madhavan/courses/pl2006/lecturenotes/lecture-notes/node28.html
通用型多态也被细分为参数多态(Parametric polymorphism)及包含多态(Inclusion polymorphism,又称子类型多态 Subtyping polymorphism)
谢谢帝球指点ww
某种意义上还真是贬义的委婉说法……
这是[Strachey67]之后搞出来的,还是加点文献吧。
这是错的,强制一直仅指隐式转换。