Je pense que je comprends les limites réelles du polymorphisme à la compilation et du polymorphisme à l'exécution. Mais quelles sont les différences conceptuelles entre les interfaces explicites (polymorphisme d'exécution. Ie les fonctions virtuelles et les pointeurs / références) et les interfaces implicites (polymorphisme de compilation. Ie les modèles) .
Mes pensées sont que deux objets qui offrent la même interface explicite doivent être du même type d'objet (ou avoir un ancêtre commun), tandis que deux objets qui offrent la même interface implicite n'ont pas besoin d'être du même type d'objet et, à l'exclusion de l'implicite l'interface qu'ils offrent tous les deux, peut avoir des fonctionnalités très différentes.
Des réflexions à ce sujet?
Et si deux objets offrent la même interface implicite, quelles raisons (outre l'avantage technique de ne pas avoir besoin de répartition dynamique avec une table de recherche de fonction virtuelle, etc.) sont là pour ne pas faire hériter ces objets d'un objet de base qui déclare cette interface, donc ce qui en fait une interface explicite ? Une autre façon de le dire: pouvez-vous me donner un cas où deux objets qui offrent la même interface implicite (et peuvent donc être utilisés comme types pour l'exemple de classe de modèle) ne devraient pas hériter d'une classe de base qui rend cette interface explicite?
Quelques articles liés:
- https://stackoverflow.com/a/7264550/635125
- https://stackoverflow.com/a/7264689/635125
- https://stackoverflow.com/a/8009872/635125
Voici un exemple pour rendre cette question plus concrète:
Interface implicite:
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();
}
};
Interface explicite:
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();
}
};
Un exemple concret encore plus approfondi:
Certains problèmes C ++ peuvent être résolus avec:
- une classe basée sur un modèle dont le type de modèle fournit une interface implicite
- une classe non basée sur un modèle qui prend un pointeur de classe de base qui fournit une interface explicite
Code qui ne change pas:
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 */ }
};
Cas 1 . Une classe non basée sur un modèle qui prend un pointeur de classe de base qui fournit une interface explicite:
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;
}
Cas 2 . Une classe basée sur un modèle dont le type de modèle fournit une interface implicite:
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;
}
Cas 3 . Une classe basée sur un modèle dont le type de modèle fournit une interface implicite (cette fois, ne dérivant pas de 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;
}
Le cas 1 requiert que l'objet transmis useCoolClass()
soit un enfant de CoolClass
(et implémente worthless()
). Les cas 2 et 3, en revanche, prendront n'importe quelle classe qui a une doSomethingCool()
fonction.
Si les utilisateurs du code étaient toujours bien classés CoolClass
, alors le cas 1 a un sens intuitif, car CoolClassUser
ils s'attendraient toujours à une implémentation de a CoolClass
. Mais supposons que ce code fasse partie d'un framework API, donc je ne peux pas prédire si les utilisateurs voudront sous CoolClass
-classer ou rouler leur propre classe qui a une doSomethingCool()
fonction.
la source
Réponses:
Vous avez déjà défini le point important: l'un est au moment de l' exécution et l'autre au moment de la compilation . Les vraies informations dont vous avez besoin sont les ramifications de ce choix.
Compiletime:
virtual
héritage ou d'autres manigances avec des interfaces implicites - un gros avantage.Durée:
Étant donné la liste relative, si vous n'avez pas besoin d'un avantage spécifique de l'héritage d'exécution, ne l'utilisez pas. Il est plus lent, moins flexible et moins sûr que les modèles.
Edit: Il convient de noter qu'en C ++ en particulier, il existe des utilisations de l'héritage autres que le polymorphisme d'exécution. Par exemple, vous pouvez hériter de typedefs, ou l'utiliser pour le balisage de type, ou utiliser le CRTP. En fin de compte, cependant, ces techniques (et d'autres) relèvent vraiment du «temps de compilation», même si elles sont mises en œuvre à l'aide
class X : public Y
.la source