restituire un tipo o come conservare un tipo di puntatore a un object?

Ho una struttura di codice molto complicata, ma i bit importanti sono:

setup tipico: ho una class base e due classi che derivano da questa class base e ognuna ha propri membri e che non hanno un costruttore standard

class BaseSolver{ ... }; class SolverA : BaseSolver{ public: std::string a; SolverA(TypeA objectA); }; class SolverB : BaseSolver{ public: int b; SolverB(TypeB objectB); }; 

Ora ho un file config xml da cui ho letto se devo usare SolverA o SolverB . Pertanto ho un IOService:

 template class IOService { BaseSolver* getSolver() { std::string variableThatIReadFromXML; /* here I have to perform many actions before I can create a solver object * to retrieve the data needed for the constructors */ TypeA variableIConstrucedWithDataFromXML; TypeB anotherVariableIConstrucedWithDataFromXML; if (variableThatIReadFromXML == "a") return new SolverA(variableIConstrucedWithDataFromXML); // I know that this can leak memory else if (variableThatIReadFromXML == "b") return new SolverB(anotherVariableIConstrucedWithDataFromXML); } }; 

E da qualche parte nella mia applicazione (per semplicità diciamo che è il main.cpp):

 int main(){ IOService ioService; BaseSolver* mySolver = ioService.getSolver(); } 

Questo è assolutamente bene.

Ma ora, nel principale, devo accedere rispettivamente ai membri delle classi derivate b . Come posso fare questo?

Ho pensato di ritirare solo il tipo di Risolutore dallo IOService:

 class IOService { decltype getSolverType() { std::string variableThatIReadFromXML; /* here I have to perform many actions before I can create a solver object * to retrieve the data needed for the constructors */ TypeA variableIConstrucedWithDataFromXML; TypeB anotherVariableIConstrucedWithDataFromXML; if (variableThatIReadFromXML == "a") return new SolverA(variableIConstrucedWithDataFromXML); // I know that this can leak memory else if (variableThatIReadFromXML == "b") return new SolverB(anotherVariableIConstrucedWithDataFromXML); } TypeA getConstructorDataForSolverA() { /* here I have to perform many actions before I can create a solver object * to retrieve the data needed for the constructors */ return variableIConstrucedWithDataFromXML; } TypeB getConstructorDataForSolverB() { /* here I have to perform many actions before I can create a solver object * to retrieve the data needed for the constructors */ return anotherVariableIConstrucedWithDataFromXML; } }; 

Ma ovviamente non posso specificare decltype come valore di ritorno.

Sono davvero impotente. Gradirei qualsiasi suggerimento nella giusta direzione o anche una soluzione per questo problema.

[ Modifica ]: le classi del risolutore derivato richiedono più che solo le informazioni dal file xml per funzionare correttamente. Ciò significa che devo impostare alcune proprietà che provengono da un file mesh. Quindi potrei dare il file mesh a IOService, in modo che IOService possa impostare i membri appropriati in questo modo:

 class IOService { BaseSolver* getSolver(MeshType myMesh) { std::string variableThatIReadFromXML; /* here I have to perform many actions before I can create a solver object * to retrieve the data needed for the constructors */ TypeA variableIConstrucedWithDataFromXML; TypeB anotherVariableIConstrucedWithDataFromXML; if (variableThatIReadFromXML == "a") { auto solverA = new SolverA(variableIConstrucedWithDataFromXML); // I know that this can leak memory solverA.a = mesh.a; } else if (variableThatIReadFromXML == "b") { auto solverB = new SolverB(anotherVariableIConstrucedWithDataFromXML); solverB.b = mesh.b; } } }; 

Ma poi l’IOService deve conoscere la class MeshType , ciò che voglio evitare, perché penso che rompa l’incapsulamento. Quindi volevo impostare i membri b , rispettivamente, in un’altra parte del mio programma (qui per semplicità nel main).

Tenendo conto di ciò, solo la risposta di Daniel Daranas sembra una soluzione per me. Ma volevo evitare i lanci dinamici.

Quindi una domanda riformulata potrebbe essere: come dovrei cambiare il mio design per garantire l’incapsulamento ed evitare i cast dinamici? [/Modificare]

Sto usando clang 3.4 ob ubuntu 12.04 lts.

La cosa divertente del polimorfismo è che ti indica quando non la usi.

Ereditare una class base nel modo in cui si è utili 1 scopo: esporre un’interfaccia uniforms per oggetti con comportamenti diversi. Fondamentalmente, vuoi che le classi figlio abbiano lo stesso aspetto. Se ho classi B e C che ereditano da A, voglio dire “fai pippo” alla class, e farò di foob o di fooc .

In sostanza, lo stai capovolgendo: ho B e C di tipo A, e se è B voglio fare la piega e se è CI voglio fare fooc. Mentre questo può sembrare spaventoso, di solito il modo migliore per risolvere il problema è riformulare la domanda.

Quindi, per il tuo esempio, stai dicendo “OK, quindi ho un file XML, e leggerò i dati da esso in un modo se sto facendo un A, o in un altro modo se sto facendo un B.” Ma il modo polimorfico sarebbe “Ho un file XML, mi dice di fare una A o una B, e poi dico all’istanza di analizzare il file XML”.

Quindi uno dei modi per risolvere questo problema per cambiare la tua interfaccia risolutore:

 class BaseSolver { public: virtual void ReadXMLFile(string xml) = 0; ... }; 

Mentre questo riformula il problema in un modo che usa il polimorfismo, e rimuove la necessità di vedere ciò che hai creato, probabilmente non ti piace per la stessa ragione per cui non lo faccio: dovresti fornire un costruttore predefinito, che lascia la class in uno stato sconosciuto.

Quindi, anziché imporlo a livello di interfaccia, è ansible applicarlo a livello di costruttore e fare in modo che SolverA e SolverB prendano la stringa XML come parte del costruttore.

Ma cosa succede se la stringa XML è male? Quindi si otterrebbe uno stato di errore nel costruttore, che è anche un no-no. Quindi mi occuperei di questo usando il modello di fabbrica:

 class SolverFactory; class BaseSolver { public: virtual void solve() = 0; protected: virtual int ReadXML(std::string xml) = 0; friend class SolverFactory; }; class A : public BaseSolver { public: virtual void solve() {std::cout << "A" << std::endl;} protected: A(){} virtual int ReadXML(std::string xml) {return 0;} friend class SolverFactory; }; class B : public BaseSolver { public: virtual void solve() {std::cout << "B" << std::endl;} protected: B(){} virtual int ReadXML(std::string xml) {return 0;} friend class SolverFactory; }; class SolverFactory { public: static BaseSolver* MakeSolver(std::string xml) { BaseSolver* ret = NULL; if (xml=="A") { ret = new A(); } else if (xml=="B") { ret = new B(); } else { return ret; } int err = ret->ReadXML(xml); if (err) { delete ret; ret = NULL; } return ret; } }; 

Non ho inserito alcuna elaborazione XML reale qui perché sono pigro, ma potresti avere il factory per ottenere il tipo dal tag principale e poi passare il resto del nodo. Questo metodo garantisce un ottimo incapsulamento, può catturare errori nel file xml e separa in modo sicuro i comportamenti che stai cercando di ottenere. Espone anche le funzioni pericolose (il costruttore predefinito e ReadXMLFile) a SolverFactory, dove tu (presumibilmente) sai cosa stai facendo.

Modifica: in risposta alla domanda

Il problema che hai dichiarato è “Ho un B e C di tipo A, e se è B voglio impostare” b “e se è C voglio impostare” c “impostazioni”.

Approfittando del polimorfismo, tu dici “Ho una B e una C di tipo A. Dico loro di ottenere le loro impostazioni”.

Ci sono un paio di modi per farlo. Se non ti dispiace maneggiare il tuo IO con la class, puoi semplicemente esporre il metodo:

 class BaseSolver { public: virtual void GetSettingsFromCommandLine() = 0; }; 

E quindi creare i singoli metodi per ogni class.

Se vuoi crearli separati, allora quello che vuoi è il polimorfismo nel io. Quindi esponilo in questo modo:

 class PolymorphicIO { public: virtual const BaseSolver& get_base_solver() const = 0; virtual void DoSettingIO() = 0; }; 

un esempio di implmentazione

 class BaseSolverBIO : PolymorphicIO { public: virtual const BaseSolver& get_base_solver() const {return b;} virtual void DoSettingIO() { char setting = get_char(); b.set_b(setting);} private: BaseSolverB b; }; 

A prima vista sembra un sacco di codice (abbiamo raddoppiato il numero di classi e probabilmente abbiamo bisogno di fornire una class factory sia per BaseSolver che per l’interfaccia IO). Perché farlo?

È il problema della scalabilità / manutenibilità. Diciamo che hai trovato un nuovo risolutore che vuoi aggiungere (D). Se utilizzi il cast dinamico, devi trovare tutti i luoghi nel tuo livello principale e aggiungere una nuova dichiarazione del caso. Se c’è solo 1 posto, questo è abbastanza facile, ma se è 10 posti, potresti facilmente dimenticarlo e sarebbe difficile rintracciarlo. Invece, con questo metodo hai una class separata che ha tutte le funzionalità IO specifiche per il risolutore.

Pensiamo anche a cosa succede a quegli assegni di dynamic_cast quando il numero di solutori cresce. Hai mantenuto questo software per anni con una grande squadra, e diciamo che hai trovato i solutori fino alla lettera Z. Ognuna di queste dichiarazioni if-else sono centinaia – una serie di linee lunghe ora: se tu hai un errore in O devi scorrere AM solo per trovare il bug. Inoltre, l’overhead per l’utilizzo del polimorfismo è costante, mentre la riflessione cresce e cresce e cresce.

Il vantaggio finale per farlo in questo modo è se hai una class BB : public B Probabilmente hai tutte le vecchie impostazioni di B, e vuoi mantenerle, basta renderle un po ‘più grandi. Usando questo modello, puoi estendere la class IO anche per io per BB e riutilizzare quel codice.

Usa dynamic_cast per provare a trasmettere una class pointer-to-base alla class pointer-to-derived. Restituirà NULL se l’object puntato della class base non esiste (valore NULL del puntatore di base), o non è in realtà un object class derivato. Se il risultato, invece, non è NULL, hai una valida class da puntatore a derivato.

 int main(){ IOService ioService; BaseSolver* mySolver = ioService.getSolver(); SolverB* bSolver = dynamic_cast(mySolver); if (bSolver != NULL) { int finallyIGotB = bSolver->b; cout << finallyIGotB; } } 

Si noti che potrebbero esserci alcune migliori soluzioni di design rispetto all'utilizzo di dynamic_cast . Ma almeno questa è una possibilità.

Un modo per ottenere ciò è aggiungere un metodo di interfaccia alla class base:

 class BaseSolver{ virtual void SolverMethodToCallFromMain() = 0; ... }; class SolverA : BaseSolver{ public: std::string a; SolverA(TypeA objectA); virtual void SolverMethodToCallFromMain() {/*SolverA stuff here*/}; }; class SolverB : BaseSolver{ public: int b; SolverB(TypeB objectB); virtual void SolverMethodToCallFromMain() {/*SolverB stuff here*/}; }; 

E in principale:

 int main(){ IOService ioService; BaseSolver* mySolver = ioService.getSolver(); mySolver->SolverMethodToCallFromMain(); }