2013-06-11 12 views
5

devo alcuni alberi di ereditarietà di base:Come risolvere quale funzione viene chiamata in base al tipo di oggetti in un contenitore?

class Base { 
    virtual double func() = 0; 
    // functionality is not important for the problem 
    // but it's good to know that Base has some virtual functions 
}; 
class DerivedA : public Base { 
    virtual double func() {}; // implementation A 
}; 
class DerivedB : public Base { 
    virtual double func() {}; // implementation B 
}; 

ho un recipiente contenente un puntatore a uno o DerivedADerivedB istanze.

void f1(std::vector<Base*> a)() { /* some code */ } 

int main(in, char**) { 
    std::vector<Base*> base_container; 
    f1(base_container); // works fine 
} 

Quasi tutto funziona sulla base, ma ho qualche funzione, non specificato nel DerivedA o DerivedB, vale a dire l'attuazione specifica, ma funziona su un contenitore DerivedA o DerivedB. Supponiamo che il seguente frammento di codice:

void f2(std::vector<DerivedA*> a)() { /* implementation A specific code */ } 
void f2(std::vector<DerivedB*> a)() { /* implementation B specific code */ } 

Qual è la procedura migliore per chiamare la funzione corretta? Elencho alcune possibili soluzioni, che mi sono venute in mente, ma presentano tutti i maggiori inconvenienti.

  1. Potrei contenere tutti i dati in contenitori di implementazione specifici. Tuttavia, f1() non funzionerà più poiché std::vector<DerivedA*> non è un figlio di std::vector<Base*> (per buoni motivi!).

  2. Potrei lanciare manualmente tutti gli oggetti tutto il tempo, ma quello è brutto.

  3. Potrei chiamare manualmente la funzione corretta che si aspetta un contenitore di Base dando loro un nome distintivo (come f2_a e f2_b), ma sembra brutto.

  4. Potrei implementare f2 come specializzazione di template di funzioni, ma ciò sembra offensivo.

  5. Potrei provare a far dipendere la funzione sul contenitore solo dalle funzioni degli oggetti nel contenitore e quindi implementare per loro funzioni sovraccariche. Questa è una buona soluzione in molti casi, ma nel mio caso è davvero una funzione sul contenitore, dando diverse implementazioni sul contenitore per DerivedA e DerivedB, non solo iterando sul contenuto del contenitore.

Come implementereste tale cosa? Come posso ottenere un container che può essere passato sia a f1() sia a f2()?

+5

Cosa farebbe 'f2()' per un contenitore che conteneva due oggetti, uno un 'DerivedA' e l'altro un' DerivedB'? –

+0

Potresti fornire un esempio del perché questo potrebbe essere utile? Non riesco a vederlo.Da un punto di vista OO, questo sembra essere disordinato. Questo potrebbe essere il motivo per cui i tuoi approcci si rivelano ugualmente brutti? –

+0

Potresti avere ragione. Sono nel machine learning niché. Quindi la mia base è un modello predittivo, mentre le classi derivate sono modelli di regressione o classificazione. Ora voglio calcolare le misure di accuratezza dei modelli e differiscono dalla classificazione e dalla regressione. – hildensia

risposta

1

La soluzione migliore è che l'ereditarietà rappresenti la sostituzione e che la classe base disponga di un'interfaccia completa che esegue tutto il lavoro, utilizzando metodi virtuali per scegliere i metodi di classe derivati ​​da utilizzare. Quindi lo f2 prende solo un vettore di puntatori di base.

Altrimenti, se ci sono davvero interfacce specifiche nelle classi derivate a cui è necessario accedere (e se ci sono, prendi un momento per riflettere su ciò che hai fatto con il tuo progetto), il tuo migliore appoach è un metodo con nomi diversi, uno per ogni tipo derivato, ognuno dei quali accetta un vettore di puntatori di base. Con metodi con nome appropriato sarà chiaro all'utente che tipo è previsto.

1

Un'altra possibilità potrebbe essere quella di creare un visitor. La funzionalità basata sul tipo derivato si troverà nel metodo overload() del visitatore anziché nella funzione overload che funziona sul contenitore completo. Funziona anche su contenitori contenenti entrambe le classi derivate.

Lo svantaggio è una certa complessità e dipendenze aggiunte.

0

si dovrebbe usato dynamic_cast che restituiscono nulla con il cattivo di conversione e utilizzare

if(dynamic_cast<DerivedA>(ptr)!=null) {...} 
else if(dynamic_cast<DerivedB>(ptr)!=null) {...} 
+1

Questo si traduce nella mia soluzione n. 3, giusto? Risolvendo manualmente alla funzione giusta. – hildensia

+0

se si dispone di un contenitore in cui si esegue il ciclo, quindi utilizzare dynamic_casting, è possibile chiamare la funzione corretta, tuttavia il parametro funzione non deve essere un contenitore, deve essere un puntatore alla classe specifica – aah134

+0

No, penso che sia la soluzione no. 2. Questa risposta suggerisce di eseguire il cast all'interno di 'f1()'. – Oktalist

1

Questo non ha senso.

Non è possibile chiamare una funzione su un vettore di tipi derivati ​​quando il contenitore di origine può contenere entrambi i tipi.

Sarà necessario suddividerli in due vettori, utilizzando il cast dinamico o controllando una sorta di funzione virtuale type(). Solo così sarai in grado di chiamare queste funzioni con sicurezza.

L'opzione ideale e più pulita è l'opzione numero 5, è il modo in cui il polimorfismo di runtime dinamico dovrebbe funzionare.

0

I modelli (o una combinazione di modelli e sovraccarico) offrono una soluzione possibile, anche se in questo caso potrebbero diventare poco maneggevoli.

template <class T> 
void f1(std::vector<T*> a) 
{ 
    // code that doesn't care whether T is Base or DerivedA or DerivedB 
} 

void f2(std::vector<DerivedA*> a) 
{ 
    // code that requires T = DerivedA 
} 
void f2(std::vector<DerivedB*> a) 
{ 
    // code that requires T = DerivedB 
} 

tenta di passare un std::vector<Base*> per f2() comporterebbe un errore di compilazione.

1
  1. Non vorrei utilizzare i puntatori grezzi. shared_ptr o un contenitore ptr_...<> da Boost per elencare alcune alternative.
  2. Non esporre il tipo di contenitore utilizzato o il fatto che gli oggetti siano memorizzati in un contenitore. Separare il contenitore camminando dalle azioni sui singoli oggetti.
  3. Implementare il modello basato su tipo di selezione basato su oggetto con una funzione virtuale, perché è a questo che servono le funzioni virtuali.

Ora torniamo dal mondo ideale al nostro sfortunato pianeta.

Se si dispone di un std::vector<Derived*> a portata di mano, basta chiamare la funzione risolta staticamente corretta per std::vector<Derived*>, sovraccaricata o denominata in modo distinto, a propria scelta.

Se tutto quello che hai è std::vector<Base*> e vi immagino che tutti i puntatori memorizzati in là in realtà puntare a Derived1*, probabilmente vuole verificare l'ipotesi in qualche modo (cosa succede se in realtà si ha un mix di Derived1*, Derived2* e chissà , forse una specie di Derived3* che è stata aggiunta dal tuo collega mentre non stavi guardando?) Come si fa? Una volta che si utilizza dynamic_cast<> per il controllo, si può anche usare dynamic_cast<> per il resto di tutto.

Se tutto quello che hai è std::vector<Base*> e sai a priori che tutti i puntatori memorizzati in là in realtà puntare a Derived1*, sono probabilmente memorizzati in un std::vector<Base*> per nessuna buona ragione (dal momento che le informazioni di tipo tanto necessaria viene persa e deve essere ricreato in modi brutti) e dovresti prendere in considerazione la possibilità di cambiarlo in std::vector<Derived1*>.

Problemi correlati