Penso di capire le reali limitazioni del polimorfismo in fase di compilazione e del polimorfismo di runtime. Ma quali sono le differenze concettuali tra interfacce esplicite (polimorfismo di runtime, ovvero funzioni virtuali e riferimenti / puntatori) e interfacce implicite (polimorfismo in fase di compilazione, ovvero modelli) .
I miei pensieri sono che due oggetti che offrono la stessa interfaccia esplicita devono essere dello stesso tipo di oggetto (o avere un antenato comune), mentre due oggetti che offrono la stessa interfaccia implicita non devono necessariamente essere dello stesso tipo di oggetto, e escludendo l'interfaccia implicita che entrambi offrono, possono avere funzionalità abbastanza diverse.
Qualche idea su questo?
E se due oggetti offrono la stessa interfaccia implicita, quali sono i motivi (oltre al vantaggio tecnico di non aver bisogno di dispacciamento dinamico w / una tabella di ricerca delle funzioni virtuali, ecc.) per non avere questi oggetti ereditati da un oggetto di base che dichiara che interfaccia, rendendola così un'interfaccia esplicita ? Un altro modo per dirlo: puoi darmi un caso in cui due oggetti che offrono la stessa interfaccia implicita (e quindi possono essere usati come tipi per la classe template di esempio) non dovrebbero ereditare da una classe base che rende esplicita quell'interfaccia?
Alcuni post correlati:
Ecco un esempio per rendere più concreta questa domanda:
Interfaccia implicita:
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();
}
};
Interfaccia esplicita:
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 esempio concreto ancora più approfondito:
Alcuni problemi C ++ possono essere risolti con:
- una classe basata su modelli il cui tipo di modello fornisce un'interfaccia implicita
- una classe non basata su modelli che accetta un puntatore di classe base che fornisce un'interfaccia esplicita
Codice che non cambia:
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 */ }
};
Caso 1 . Una classe non basata su modelli che accetta un puntatore di classe base che fornisce un'interfaccia esplicita:
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;
}
Caso 2 . Una classe basata su modelli il cui tipo di modello fornisce un'interfaccia implicita:
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;
}
Caso 3 . Una classe basata su modelli il cui tipo di modello fornisce un'interfaccia implicita (questa volta, non derivante da 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;
}
Il caso 1 richiede che l'oggetto passato a useCoolClass()
sia figlio di CoolClass
(e implementa worthless()
). Casi 2 e 3, d'altra parte, prenderanno qualsiasi classe che ha una funzione doSomethingCool()
.
Se gli utenti del codice erano sempre sottoclassi di CoolClass
, allora il caso 1 ha un senso intuitivo, dal momento che CoolClassUser
si aspetta sempre un'implementazione di CoolClass
. Ma supponiamo che questo codice farà parte di un framework API, quindi non posso prevedere se gli utenti vorranno sottoclasse CoolClass
o rollare la propria classe che ha una funzione doSomethingCool()
.