Interfacce esplicite vs esplicite

5

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:

  1. una classe basata su modelli il cui tipo di modello fornisce un'interfaccia implicita
  2. 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() .

    
posta Chris Morris 10.02.2012 - 19:03
fonte

1 risposta

4

Hai già definito il punto importante: tempo di esecuzione e l'altro è tempo di compilazione . Le informazioni reali di cui hai bisogno sono le ramificazioni di questa scelta.

compiletime:

  • Pro: le interfacce in fase di compilazione sono molto più granulari di quelle in fase di esecuzione. Con ciò, ciò che intendo è che puoi utilizzare solo i requisiti di una singola funzione o un insieme di funzioni, come le chiami tu. Non devi fare sempre l'intera interfaccia. I requisiti sono solo ed esattamente ciò di cui hai bisogno.
  • Pro: Tecniche come CRTP significano che è possibile utilizzare interfacce implicite per implementazioni predefinite di cose come gli operatori. Non potresti mai fare una cosa simile con l'ereditarietà di runtime.
  • Pro: le interfacce implicite sono molto più facili da comporre e moltiplicare "ereditare" rispetto alle interfacce di runtime e non impongono alcun tipo di restrizioni binarie, ad esempio, le classi POD possono utilizzare interfacce implicite . Non c'è bisogno di ereditarietà virtual o altri shenanigans con interfacce implicite, un grande vantaggio.
  • Pro: il compilatore può fare molto più ottimizzazioni per le interfacce in fase di compilazione. Inoltre, la sicurezza del tipo extra rende più sicuro il codice.
  • Pro: È impossibile scrivere valori di valore per le interfacce run-time, perché non si conoscono le dimensioni o l'allineamento dell'oggetto finale. Ciò significa che ogni caso che richiede / benefici dalla tipizzazione dei valori ottiene grandi vantaggi dai modelli.
  • Contro: i modelli sono una cagna da compilare e utilizzare e possono essere portati con cautela tra i compilatori
  • Con: i modelli non possono essere caricati in fase di esecuzione (ovviamente), quindi hanno dei limiti nell'espressione di strutture dati dinamiche, ad esempio.

Durata:

  • Pro: il tipo finale non deve essere deciso prima dell'esecuzione. Ciò significa che l'ereditarietà di runtime può esprimere alcune strutture di dati molto più facilmente, se i modelli possono farlo affatto. Inoltre è possibile esportare i tipi polimorfici di runtime attraverso i limiti C, ad esempio COM.
  • Pro: È molto più semplice specificare e implementare l'ereditarietà di runtime e non otterrai alcun comportamento specifico del compilatore.
  • Con: l'ereditarietà in fase di esecuzione può essere più lenta di quella in fase di compilazione.
  • Con: l'eredità run-time perde le informazioni sul tipo.
  • Con: l'ereditarietà di runtime è molto meno flessibile.
  • Contro: l'ereditarietà multipla è una cagna.

Dato l'elenco relativo, se non hai bisogno di un vantaggio specifico dell'eredità run-time, non usarlo. È più lento, meno flessibile e meno sicuro dei modelli.

Modifica: vale la pena notare che in C ++ in particolare ci sono usi per l'ereditarietà altro del polimorfismo di runtime. Ad esempio, è possibile ereditare typedef o utilizzarlo per la codifica del tipo o utilizzare il CRTP. In definitiva, tuttavia, queste tecniche (e altre) ricadono realmente in "Tempo di compilazione", anche se sono implementate utilizzando class X : public Y .

    
risposta data 10.02.2012 - 19:36
fonte

Leggi altre domande sui tag