Le classi astratte problema in C ++ undo / redo implementazione
-
20-09-2019 - |
Domanda
Ho definito una "Azione" classe astratta pura in questo modo:
class Action {
public:
virtual void execute () = 0;
virtual void revert () = 0;
virtual ~Action () = 0;
};
E rappresentato ogni comando l'utente può eseguire con una classe.
Per effettiva undo / redo mi piacerebbe fare qualcosa di simile:
Annulla
Action a = historyStack.pop();
a.revert();
undoneStack.push(a);
Ripristina
Action a = undoneStack.pop();
a.execute();
historyStack.push(a);
Il compilatore, ovviamente, non accetta questo, perché "Azione" è una classe astratta che non può essere istantiated.
Quindi, devo ridisegnare tutto o c'è una semplice soluzione a questo problema?
Soluzione
È possibile memorizzare le azioni come i puntatori, che manterrà il compilatore felice.
std::vector<Action*> historyStack;
/*...*/
historyStack.push_back(new EditAction(/*...*/));
Action* a = historyStack.pop();
a->revert();
undoneStack.push(a);
C'è un altro motivo per cui std::vector<Action> historyStack;
non funziona e questo è affettare. Quando si aggiungono oggetti di classi derivate al vettore saranno gettati alla classe di base e sciolto tutto il loro polimorfismo. Di più qui: Ciò che è oggetto affettare
Modifica considerare di usare ptr_vector per gestire il ciclo di vita degli oggetti nel vettore: http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/tutorial.html
Altri suggerimenti
spedizione polimorfico avviene solo tramite puntatori o riferimenti in C ++ in ogni caso. Potrebbe non essere in grado di creare un valore di azione, ma troverete che sarete in grado di creare riferimenti e puntatori a azioni.
pop deve semplicemente restituire un puntatore (possibilmente intelligente), o un riferimento, ad un'azione. Un approccio potrebbe essere quello di utilizzare std :: auto_ptr e spinta :: ptr_deque , tale volontà (con l'uso corretto) assicurare che le azioni siano adeguatamente puliti dopo.
std::auto_ptr<Action> a = historyStack.pop_front();
a->revert();
undoneStack.push_front(a);
Un'altra opzione potrebbe essere un std::stack
di boost::shared_ptr<Action>
o simili. Oppure si può semplicemente utilizzare i puntatori prime, ma è necessario fare attenzione che la proprietà è gestita correttamente.
È possibile memorizzare i puntatori alle operazioni eseguite nella coda.
Per esempio:
std::vector<Action*> historyStack;
std::vector<Action*> undoneStack;
Quindi:
Action* a = historyStack.pop_back();
a->revert();
undoneStack.push_back( a );
E
Action* a = undoneStack.pop_back();
a->execute();
historyStack.push_back(a);
Ovviamente si deve usare nuovo e eliminare per creare e liberare memoria per effettivi Azione oggetti e non credo che si può usare auto_ptr con contenitori standard in modo da avere di gestire la memoria manualmente o implementare qualche altro metodo. Ma questo non dovrebbe essere un grosso problema se si avvolgono buffer di annullamento in una classe.