Ich glaube, ich verstehe die tatsächlichen Einschränkungen des Polymorphismus zur Kompilierungszeit und des Laufzeitpolymorphismus. Aber was sind die konzeptionellen Unterschiede zwischen expliziten Schnittstellen (Laufzeit - Polymorphismus. Dh virtuelle Funktionen und Zeiger / Referenzen) und implizite Schnittstellen (Kompilierung-Polymorphismus. Dh. Vorlagen) .
Ich bin der Meinung, dass zwei Objekte, die dieselbe explizite Schnittstelle bieten, denselben Objekttyp haben müssen (oder einen gemeinsamen Vorfahren haben müssen), während zwei Objekte, die dieselbe implizite Schnittstelle bieten, nicht denselben Objekttyp haben müssen und den impliziten ausschließen Schnittstelle, die beide bieten, können ganz unterschiedliche Funktionen haben.
Irgendwelche Gedanken dazu?
Und wenn zwei Objekte dieselbe implizite Schnittstelle bieten, welche Gründe (neben dem technischen Vorteil, dass kein dynamischer Versand mit einer Nachschlagetabelle für virtuelle Funktionen usw. erforderlich ist) liegen darin, dass diese Objekte nicht von einem Basisobjekt geerbt werden, das diese Schnittstelle deklariert macht es eine explizite Schnittstelle? Eine andere Art, es auszudrücken: Können Sie mir einen Fall nennen, in dem zwei Objekte, die dieselbe implizite Schnittstelle bieten (und daher als Typen für die Beispielvorlagenklasse verwendet werden können), nicht von einer Basisklasse erben sollten, die diese Schnittstelle explizit macht?
Einige verwandte Beiträge:
- https://stackoverflow.com/a/7264550/635125
- https://stackoverflow.com/a/7264689/635125
- https://stackoverflow.com/a/8009872/635125
Hier ist ein Beispiel, um diese Frage konkreter zu machen:
Implizite Schnittstelle:
class Class1
{
public:
void interfaceFunc();
void otherFunc1();
};
class Class2
{
public:
void interfaceFunc();
void otherFunc2();
};
template <typename T>
class UseClass
{
public:
void run(T & obj)
{
obj.interfaceFunc();
}
};
Explizite Schnittstelle:
class InterfaceClass
{
public:
virtual void interfaceFunc() = 0;
};
class Class1 : public InterfaceClass
{
public:
virtual void interfaceFunc();
void otherFunc1();
};
class Class2 : public InterfaceClass
{
public:
virtual void interfaceFunc();
void otherFunc2();
};
class UseClass
{
public:
void run(InterfaceClass & obj)
{
obj.interfaceFunc();
}
};
Ein noch ausführlicheres, konkretes Beispiel:
Einige C ++ - Probleme können entweder gelöst werden mit:
- Eine Vorlagenklasse, deren Vorlagentyp eine implizite Schnittstelle bereitstellt
- Eine Klasse ohne Vorlage, die einen Basisklassenzeiger verwendet, der eine explizite Schnittstelle bereitstellt
Code, der sich nicht ändert:
class CoolClass
{
public:
virtual void doSomethingCool() = 0;
virtual void worthless() = 0;
};
class CoolA : public CoolClass
{
public:
virtual void doSomethingCool()
{ /* Do cool stuff that an A would do */ }
virtual void worthless()
{ /* Worthless, but must be implemented */ }
};
class CoolB : public CoolClass
{
public:
virtual void doSomethingCool()
{ /* Do cool stuff that a B would do */ }
virtual void worthless()
{ /* Worthless, but must be implemented */ }
};
Fall 1 . Eine Klasse ohne Vorlage, die einen Basisklassenzeiger verwendet, der eine explizite Schnittstelle bietet:
class CoolClassUser
{
public:
void useCoolClass(CoolClass * coolClass)
{ coolClass.doSomethingCool(); }
};
int main()
{
CoolA * c1 = new CoolClass;
CoolB * c2 = new CoolClass;
CoolClassUser user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Fall 2 . Eine Vorlagenklasse, deren Vorlagentyp eine implizite Schnittstelle bietet:
template <typename T>
class CoolClassUser
{
public:
void useCoolClass(T * coolClass)
{ coolClass->doSomethingCool(); }
};
int main()
{
CoolA * c1 = new CoolClass;
CoolB * c2 = new CoolClass;
CoolClassUser<CoolClass> user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Fall 3 . Eine Vorlagenklasse, deren Vorlagentyp eine implizite Schnittstelle bereitstellt (diesmal nicht abgeleitet von CoolClass
:
class RandomClass
{
public:
void doSomethingCool()
{ /* Do cool stuff that a RandomClass would do */ }
// I don't have to implement worthless()! Na na na na na!
}
template <typename T>
class CoolClassUser
{
public:
void useCoolClass(T * coolClass)
{ coolClass->doSomethingCool(); }
};
int main()
{
RandomClass * c1 = new RandomClass;
RandomClass * c2 = new RandomClass;
CoolClassUser<RandomClass> user;
user.useCoolClass(c1);
user.useCoolClass(c2);
return 0;
}
Fall 1 erfordert, dass das übergebene Objekt useCoolClass()
ein Kind von CoolClass
(und ein Implementierungsobjekt worthless()
) ist. Die Fälle 2 und 3 nehmen dagegen jede Klasse an, die eine doSomethingCool()
Funktion hat.
Wenn Benutzer des Codes immer eine feine Unterklasse hätten CoolClass
, wäre Fall 1 intuitiv sinnvoll, da CoolClassUser
immer eine Implementierung von a erwartet würde CoolClass
. Angenommen, dieser Code ist Teil eines API-Frameworks, sodass ich nicht vorhersagen kann, ob Benutzer CoolClass
ihre eigene Klasse mit einer doSomethingCool()
Funktion in Unterklassen unterteilen oder rollen möchten .
quelle
Antworten:
Sie haben bereits den wichtigen Punkt definiert - einer ist die Laufzeit und der andere ist die Kompilierungszeit . Die wirkliche Information, die Sie benötigen, sind die Konsequenzen dieser Wahl.
Kompilierungszeit:
virtual
Vererbung oder andere Spielereien mit impliziten Schnittstellen - ein großer Vorteil.Laufzeit:
Verwenden Sie die relative Liste nicht, wenn Sie keinen bestimmten Vorteil der Laufzeitvererbung benötigen. Es ist langsamer, weniger flexibel und weniger sicher als Vorlagen.
Bearbeiten: Es ist erwähnenswert, dass es insbesondere in C ++ andere Verwendungszwecke als den Laufzeitpolymorphismus gibt. Sie können beispielsweise typedefs erben oder zum Typ-Tagging verwenden oder das CRTP verwenden. Letztendlich fallen diese Techniken (und andere) jedoch wirklich unter "Kompilierungszeit", obwohl sie mit implementiert werden
class X : public Y
.quelle