2010-01-12 5 views
2

J'ai défini un pur « Action » classe abstraite comme ceci:classes abstraites d'émission en C++ Undo/Redo mise en œuvre

class Action { 
public: 
    virtual void execute() = 0; 
    virtual void revert() = 0; 
    virtual ~Action() = 0; 
}; 

Et représentés chaque commande, l'utilisateur peut exécuter avec une classe.

Pour Undo/Redo réelle Je voudrais faire quelque chose comme ceci:

Annuler

Action a = historyStack.pop(); 
a.revert(); 
undoneStack.push(a); 

Redo

Action a = undoneStack.pop(); 
a.execute(); 
historyStack.push(a); 

Le compilateur ne peut évidemment accepter pas, parce que "Action" est une classe abstraite qui ne peut être istancée.

Alors, dois-je tout repenser ou existe-t-il une solution simple à ce problème?

Répondre

8

Vous devez stocker des actions en tant que pointeurs, ce qui maintiendra le compilateur satisfait.

std::vector<Action*> historyStack; 
/*...*/ 
historyStack.push_back(new EditAction(/*...*/)); 
Action* a = historyStack.pop(); 
a->revert(); 
undoneStack.push(a); 

Il y a une autre raison pour laquelle std::vector<Action> historyStack; ne fonctionnera pas et c'est trancher. Lors de l'ajout d'objets de classes dérivées au vecteur, ils seront castés dans la classe de base et perdront tout leur polymorphisme. Pour en savoir plus ici: What is object slicing?

EDIT regarder dans ptr_vector pour gérer la durée de vie des objets dans le vecteur: http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/tutorial.html

+1

N'oubliez pas de supprimer. Les pointeurs intelligents sont utiles, tout comme les conteneurs spécifiques aux pointeurs tels que Boost Pointer Containers. – GManNickG

+0

Oui, merci, un conteneur de pointeur intelligent sera certainement nécessaire ici. –

+0

Cependant, avec ces fonctions, vous voulez une pile, pas un vecteur. :) – GManNickG

0

Vous devez stocker des pointeurs sur les opérations effectuées dans la file d'attente.

Par exemple:

std::vector<Action*> historyStack; 
std::vector<Action*> undoneStack; 

Puis:

Action* a = historyStack.pop_back(); 
a->revert(); 
undoneStack.push_back(a); 

Et:

Action* a = undoneStack.pop_back(); 
a->execute(); 
historyStack.push_back(a); 

Bien sûr, vous devez utiliser nouveau et supprimer pour créer et mémoire libre pour réelle Action objets et je ne pense pas que vous pouvez utiliser auto_ptr avec des conteneurs standard de sorte que vous devez gérer manuellement votre mémoire ou implémenter une autre méthode. Mais cela ne devrait pas être un gros problème si vous encapsulez un buffer dans une classe.

+0

Ou boost :: shared_ptr ot std :: tr1 :: shared_ptr si vous voulez être prudent avec les laisses de mémoire. Theres beaucoup de recommandations pour ne pas stocker les pointeurs bruts dans les conteneurs STL sauf si vous ne pouvez vraiment pas l'éviter. –

+0

Existe-t-il une raison particulière pour une telle recommandation, à l'exception de la norme que les pointeurs bruts sont difficiles à gérer? –

+0

Probablement pas. Difficile à manipuler signifie qu'il est facile à manipuler, ce qui permet également de gagner du temps. – GManNickG

0

expédition polymorphes ne se produit que par des pointeurs ou des références en C++ de toute façon. Vous ne pourrez peut-être pas créer une valeur d'Action, mais vous constaterez que vous serez en mesure de créer des références et des pointeurs vers Actions. Pop a simplement besoin de renvoyer un pointeur (éventuellement intelligent), ou une référence, à une action. Une approche pourrait être d'utiliser std :: auto_ptr et boost::ptr_deque, cela (avec une utilisation correcte) s'assurer que les actions sont correctement nettoyées après.

std::auto_ptr<Action> a = historyStack.pop_front(); 
a->revert(); 
undoneStack.push_front(a); 

Une autre option pourrait être un std::stack de boost::shared_ptr<Action> ou similaire. Ou vous pouvez simplement utiliser des pointeurs bruts, mais vous devez veiller à ce que la propriété soit correctement gérée.

+1

Attention, vous ne devriez jamais stocker auto_ptr dans un conteneur STL . Des données peuvent être perdues lorsque le conteneur est redimensionné automatiquement. Voir http://www.devx.com/tips/Tip/13606 Notez que Logan ne suggère pas cela, mais il serait facile de faire cette erreur. –