Domanda

Diciamo che ho la seguente struttura di classe:

class Car;
class FooCar : public Car;
class BarCar : public Car;

class Engine;
class FooEngine : public Engine;
class BarEngine : public Engine;

Diamo anche un Car una maniglia al suo Engine . Un FooCar verrà creato con un FooEngine * e un BarCar verrà creato con un BarEngine * . C'è un modo per sistemare le cose in modo che un oggetto FooCar possa chiamare le funzioni membro di FooEngine senza downcast?

Ecco perché la struttura delle classi è strutturata così com'è adesso:

  1. Tutte le auto hanno un motore . Inoltre, un FooCar utilizzerà sempre e comunque un FooEngine .
  2. Ci sono dati e algoritmi condivisi da tutti i Engine che preferirei non copiare e incollare.
  3. Potrei voler scrivere una funzione che richiede un Engine per conoscere il suo Car .

Non appena ho digitato dynamic_cast quando ho scritto questo codice, sapevo che probabilmente stavo facendo qualcosa di sbagliato. C'è un modo migliore per farlo?

UPDATE:

Sulla base delle risposte fornite finora, mi sto orientando verso due possibilità:

  1. Avere Auto fornire una funzione getEngine () virtuale. Ciò consentirebbe a FooCar e BarCar di avere implementazioni che restituiscono il tipo corretto di Engine .
  2. Assorbi tutte le funzionalità Engine nella struttura di ereditarietà Auto . Engine è stato creato per motivi di manutenzione (per conservare gli oggetti Engine in un luogo separato). È un compromesso tra avere più classi piccole (piccole in righe di codice) rispetto ad avere meno classi grandi.

Esiste una forte preferenza della comunità per una di queste soluzioni? C'è una terza opzione che non ho preso in considerazione?

È stato utile?

Soluzione

Suppongo che Car abbia un puntatore Engine, ed è per questo che ti ritrovi a effettuare il downcasting.

Estrai il puntatore dalla tua classe base e sostituiscilo con una pura funzione get_engine () virtuale. Quindi FooCar e BarCar possono tenere puntatori al tipo di motore corretto.

(Modifica)

Perché funziona:

Poiché la funzione virtuale Car :: get_engine () restituisce un riferimento o un puntatore , C ++ consentirà alle classi derivate di implementare questa funzione con un diverso tipo restituito , purché il tipo restituito differisca solo per essere un tipo più derivato.

Questo si chiama tipi di ritorno covarianti e consentirà ogni Car digitare per restituire il motore corretto.

Altri suggerimenti

Solo una cosa che volevo aggiungere: questo design ha già un cattivo odore per via di ciò che chiamo alberi paralleli .

Fondamentalmente se finisci con gerarchie di classi parallele (come hai fatto con Car and Engine) allora stai solo chiedendo problemi.

Vorrei ripensare se Engine (e persino Car) deve avere sottoclassi o se sono tutti solo istanze diverse delle stesse rispettive classi base.

Puoi anche modellare il tipo di motore come segue

template<class EngineType>
class Car
{
    protected:
        EngineType* getEngine() {return pEngine;}
    private:
        EngineType* pEngine;
};

class FooCar : public Car<FooEngine>

class BarCar : public Car<BarEngine>

Non vedo perché un'auto non possa essere composta da un motore (se BarCar conterrà sempre BarEngine). Il motore ha un rapporto piuttosto forte con l'auto. Preferirei:

class BarCar:public Car
{
   //.....
   private:
     BarEngine engine;
}

Sarebbe possibile per FooCar utilizzare BarEngine?

Altrimenti, potresti voler usare un AbstractFactory per creare l'oggetto giusto per l'auto, con il motore giusto.

È possibile memorizzare FooEngine in FooCar, BarEngine in BarCar

class Car {
public:
  ...
  virtual Engine* getEngine() = 0;
  // maybe add const-variant
};

class FooCar : public Car
{
  FooEngine* engine;
public:
  FooCar(FooEngine* e) : engine(e) {}
  FooEngine* getEngine() { return engine; }
};

// BarCar similarly

Il problema con questo approccio è che ottenere un motore è una chiamata virtuale (se ne sei preoccupato) e un metodo per impostare un motore in Car richiederebbe il downcasting.

Penso che dipenda se Engine è usato solo privatamente da Car e dai suoi figli o se vuoi usarlo anche in altri oggetti.

Se le funzionalità di Engine non sono specifiche per Car , utilizzerei un metodo virtual Engine * getEngine () invece di mantenere un puntatore nella classe base.

Se la sua logica è specifica per Car , preferirei mettere i dati / la logica Engine comuni in un oggetto separato (non necessariamente polimorfico) e mantenere Implementazione di FooEngine e BarEngine nelle rispettive classi secondarie Car .

Quando il riciclo dell'implementazione è più necessario dell'ereditarietà dell'interfaccia, la composizione degli oggetti offre spesso maggiore flessibilità.

Il COM di Microsoft è un po 'goffo, ma ha un nuovo concetto: se hai un puntatore a un'interfaccia di un oggetto, puoi interrogarlo per vedere se supporta altre interfacce usando QueryInterface . L'idea è quella di suddividere la classe Engine in più interfacce in modo che ciascuna possa essere utilizzata in modo indipendente.

Consentendo di non aver perso qualcosa, questo dovrebbe essere piuttosto banale.

Il modo migliore per farlo è creare pure funzioni virtuali in Engine, che sono quindi richieste nelle classi derivate che devono essere istanziate.

Probabilmente una soluzione di credito in più sarebbe quella di avere un'interfaccia chiamata IEngine, che invece passi alla tua Auto, e ogni funzione in IEngine è pura virtuale. Potresti avere un "BaseEngine" che implementa alcune delle funzionalità che desideri (ad esempio, "condiviso") e quindi sfogliarne una.

L'interfaccia è carina se dovessi mai avere qualcosa che desideri "sembrare" come un motore, ma probabilmente non lo è (ad esempio classi di test simulati e simili).

  

C'è un modo per organizzare le cose in modo che un oggetto FooCar possa chiamare le funzioni membro di FooEngine senza downcast?

In questo modo:

class Car
{
  Engine* m_engine;
protected:
  Car(Engine* engine)
  : m_engine(engine)
  {}
};

class FooCar : public Car
{
  FooEngine* m_fooEngine;
public:
  FooCar(FooEngine* fooEngine)
  : base(fooEngine)
  , m_fooEngine(fooEngine)
  {}
};
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top