Martin Sulzmann
Polymorphie (griechisch Vielgestaltigkeit) ist ein Programmierkonzept. Zweck: Einheitliche Schnittstelle welche auf verschiedene Typen anwendbar ist. Es gibt verschiedene Formen von Polymorhpie, z.B.
Subtypen Polymorphie
Coercive subtyping
Nominales subtyping
Ad-hoc Polymorphie
Parametrische Polymorphie
Generics al Java
Templates
Allgemeines Subtyping Prinzip:
Typ ist ein Subtyp von geschrieben
In jedem Kontext in welchem ein Wert vom Typ erwartet wird, kann auch ein Wert vom Type verwendet werden
Hier Coercive Subtyping: char int float double
int func(float x) {
if (x < 1.0) return 1;
return 0;
}
void testCoerce() {
int arg = 1;
float res;
// Typkorrekt weil int <= float,
// d.h. jeder Wert vom Typ int kann auch an der
// Programmstelle verwendet werden, an welcher float erwartet wird.
res = func(arg);
}
// Compiler fuegt explizite Coercions ein!
// Hier wird das ganze simuliert durch die Funktion coerce.
float coerce(int x) {
return (float)x;
}
void testCoerceTranslated() {
int arg = 1;
float res;
res = coerce(func(coerce(arg)));
}
int main() {
testCoerce();
testCoerceTranslated();
return 1;
}
Subtypbeziehungen abgeleitet as Klassendeklaration
Auch genannt “nominales” Subtyping
Betrachte Typkonstruktoren, z.B. Pointer Typen.
Welche Beziehung gilt zwischen int*
und
float*
?
Was ist mit B*
und A*
?
Pointer Subtyping is kovariant!
Falls dann .
Wieso kovariant? Damit man “generischen” Code schreiben kann, siehe Generische Datentypen und Funktionen in C
// B <= A
// abgeleitet aus Klassendeklaration, in der Literatur als "nominal subtyping" bekannt.
class A {};
class B : public A {};
void h(A x) {}
void g(B x) {}
void testAB() {
A a;
B b;
h(a); // OK
h(b); // OK, weil B <= A
// g(a); // NICHT OK
g(b); // OK
}
// Abgeleitete Subtypbeziehung:
// B <= A impliziert B* <= A*
void h2(A* x) {}
void g2(B* x) {}
void testAB_2() {
A* a = new A();
B* b = new B();
h2(a); // OK
h2(b); // OK, weil B <= A und daher auch B* <= A*
// g2(a); // NICHT OK
g2(b); // OK
delete a;
delete b;
}
// Das gleiche gilt auch fuer Arrays.
void h3(A x[]) {}
void g3(B y[]) {}
void testAB_3() {
A a[] = { A(), A(), B() }; // B <= A
B b[] = { B() };
h3(b); // OK, weil aus B <= A folgt B[] <= A[]
g3(b);
// g3(a); // Nicht OK
}
int main() {
testAB();
testAB_2();
testAB_3();
return 1;
}
Array Subtyping in Java ist auch kovariant.
Wieso? Weil man damit “generische” Bibliotheken in Java 4 (for Java Generics) schreiben konnte.
Aber, das folgende Programm liefert einen Laufzeitfehler.
Gleicher Funktions-/Operator-Name aber verschiedene Verwendung (Anzahl Argumente und deren Typen).
C++ Regel: Instanz muss eindeutig bestimmbar sein via Argumenttypen.
#include <iostream>
#include <string>
using namespace std;
int funny(int x, int y) {
return x;
}
char funny(char x, char y) {
return y;
}
void testFunny() {
cout << funny(1,2);
cout << funny('a', 'b');
// cout << funny(1, 'a'); // Ambiguous, nicht klar welche Instanz wir wollen
}
/*
// Folgendes ist nicht erlaubt, weil
// "functions that differ only in their return type cannot be overloaded"
int funny2() {
return 1;
}
bool funny2() {
return true;
}
void testFunny2() {
bool x = funny2();
int y = funny2();
}
*/
int main() {
testFunny();
}
C++ verwendet “Monomorphisation”: Für jede Instanz dupliziere den Programmcode.
Vorteil. Effizient, da keine Typecasts notwendig sind. Typ-spezifischen Optimierungen etc.
Nachteil. Codeduplikation
Java verwendet ein “generisches” Übersetzungsscheme: Ersetze jeden Typparameter durch Object.
Vorteil. “Generischer” Code bleibt erhalten.
Nachteil. Typchecks zur Laufzeit sind notwendig
// requires C++11
#include <iostream>
#include <string>
using namespace std;
// C++ Templates und Vergleich zu Java generics
/////////////////////////////////////////////////
/////////////////////////////
// Templates
template<typename T>
void mySwap(T& x, T&y) {
T tmp;
tmp = x; x = y; y = tmp;
}
void testTmp() {
int x = 1;
int y = 2;
mySwap<int>(x,y); // Instanzierung
float u = 1.0;
float v = 2.0;
mySwap(u,v); // Instanz <float> kann inferriert werden mit Hilfe der Argumente.
}
/*
C++ verwendet "Monomorphisation".
- Für jede Instanz dupliziere den Programmcode
- Vorteil. Effizient, da keine Typecasts
notwendig sind. Typ-spezifischen Optimierungen etc.
- Nachteil. Codeduplikation
Hier am Beispiel von oben.
*/
void mySwap_int(int& x, int&y) {
int tmp;
tmp = x; x = y; y = tmp;
}
void testTmp_mono() {
int x = 1;
int y = 2;
mySwap_int(x,y);
}
// Weiteres Template Beispiel.
template<typename T>
class Elem {
T val;
public:
Elem(T init) {
val = init;
}
void print() {
cout << "\n" << val;
}
void replace(T x) {
val = x;
}
};
void testElem() {
Elem<int> e(1);
Elem<string> s("Hello");
e.print();
e.replace(2);
e.print();
s.print();
s.replace("Hallo");
s.print();
}
// Beachte.
// Kein Typchecking von templates!
// Nur Typchecking von Code erhalten
// durch Monomorphisierung.
struct Point {
int x;
int y;
};
void testElem2() {
Elem<struct Point> p({1,2}); // (P)
// Kein Typfehler.
// Erst durch Hinzunahme folgender Codezeile.
// p.print();
}
// Beachte:
// Templates sind verschieden von "generics" in Java.
// In C++
// - Monomorpization
// In Java
// - Generische Uebersetzung
// Java's generisches Uebersetzungs Schema am Beispiel.
// 1. Ersetze alle Typparameter durch void*.
// (In Java wird Object verwendet).
class Elem_G {
void* val;
public:
Elem_G(void* init) {
val = init;
}
void print() {
cout << "\n" << val;
}
void replace(void* x) {
val = x;
}
};
// Passt alles?
void testElem_G() {
int i = 1;
Elem_G e(&i);
// int* <= void*
e.print();
int j = 2;
e.replace(&j);
e.print();
}
// Beachte.
// Keine korrekte Behandlung von print.
// Benoetigen "run-time type info"
// im Falle von "<<"
enum TYPE {
INT,
BOOL,
// and so on
};
class Elem_GG {
void* val;
enum TYPE t;
public:
Elem_GG(void* init, enum TYPE ty) {
val = init;
t = ty;
}
void print() {
// Entspricht "instanceof" in Java.
switch (t) {
case INT: {
int* p = (int*)(val);
cout << "\n" << *p;
break;
}
case BOOL: {
bool* p = (bool*)(val);
cout << "\n" << *p;
break;
}
}
}
void replace(void* x) {
val = x;
}
};
void testElem_GG() {
int i = 1;
Elem_GG e(&i, INT);
// int* <= void*
e.print();
int j = 2;
e.replace(&j);
e.print();
bool b = true;
Elem_GG f(&b,BOOL);
f.print();
}
/*
Zusammengefasst.
Monomorphization in C++
+ Effizient
- Code Duplikation
Generische Uebersetzung in Java
+ Generischer Code
+- Typchecks zur Laufzeit sind notwendig
*/
int main() {
// testAdd();
testElem();
testElem_G();
testElem_GG();
}