2010-09-20 3 views
3

Lorsque j'ai une méthode qui appelle un ensemble de méthodes offrant une forte garantie, j'ai souvent des problèmes pour annuler les modifications afin d'avoir également une méthode de garantie solide. Utilisons un exemple:Méthode de garantie forte faisant appel à des méthodes de garantie fortes

// Would like this to offer strong guarantee 
void MacroMethod() throw(...) 
{ 
    int i = 0; 
    try 
    { 
    for(i = 0; i < 100; ++i) 
     SetMethod(i); // this might throw 
    } 
    catch(const std::exception& _e) 
    { 
    // Undo changes that were done 
    for(int j = i; j >= 0; --j) 
     UnsetMethod(j); // this might throw 
    throw; 
    } 
} 

// Offers strong guarantee 
void SetMethod(int i) throw(...) 
{ 
    // Does a change on member i 
} 

// Offers strong guarantee 
void UnsetMethod() throw(...) 
{ 
    // Undoes a change on member i 
} 

Évidemment, le UnsetMethod pourrait lancer. Dans ce cas, mon MacroMathod() offre uniquement une garantie de base. Pourtant, j'ai fait tout ce que je pouvais pour offrir une garantie solide, mais je ne peux pas être absolument sûr que mon UnsetMethod() ne jettera pas. Voici mes questions:

  1. Dois-je même essayer d'offrir une forte garantie dans ce cas?
  2. Dois-je documenter mon MacroMethod() comme ayant une garantie basique ou forte? Même si c'est très improbable que UnsetMethod va lancer?
  3. Pouvez-vous voir un moyen de faire de cette méthode une véritable garantie?
  4. Je devrais probablement mettre l'appel à UnsetMethod() dans un essai, mais cela semble plutôt lourd, et que dois-je faire dans le catch?

Merci!

+0

Je ne l'ai vu la forte garantie été appliquée aux méthodes non fonctions autoportants. Je suppose que cela peut être fait mais nous avons vraiment besoin de mieux comprendre le contexte. Changer le membre i sur quoi exactement existe-t-il une variable globale sur laquelle nous changeons l'état? Si c'est un objet. Ensuite, vous faites une copie. Préformez les opérations si elles fonctionnent toutes, puis échangez avec la vraie. –

+0

Comme une note de style (avec des implications assez désagréables), vous devriez reconsidérer en utilisant des spécifications d'exception. Ils ont été connus pour causer de sérieux problèmes au point d'essayer de les supprimer complètement de la langue. –

+0

@Stanley: A eu une discussion avec un ami à ce sujet. J'ai aussi testé avec le compilateur, et lu le boost rationnel. Tu as raison. Je pensais que cette critique/limitation consistait à spécifier des types dans la spécification, pas la spécification tout à fait. Ce qui explique pourquoi je l'ai gardé pour lancer() vs lancer (...). Merci pour le commentaire et mettra à jour mon code. – Geeho

Répondre

6

Un bon modèle pour essayer de réaliser ceci est de faire fonctionner votre méthode sur une copie de l'objet que vous voulez modifier. Quand toutes les modifications sont faites, vous échangez les objets (le swap devrait être garanti de ne pas être lancé). Cela n'a de sens que si la copie et l'échange peuvent être implémentés efficacement.

Cette méthode présente l'avantage de ne pas avoir besoin de try...catch -blocs dans votre code, ni de code de nettoyage. Si une exception est levée, la copie modifiée est supprimée lors du déroulement de la pile et l'original n'a pas été modifié du tout.

+0

Mon exemple n'a pas bien décrit toute l'étendue du problème, sans parler du problème de performance que cette solution causerait: le membre est un tableau de données et de telles opérations peuvent être effectuées régulièrement. Mais la solution que vous avez proposée m'amène à la bonne solution, qui est essentiellement ce que vous proposez. Je vais ajouter une méthode SetCachedMethod() et ApplyCache()/ClearCache(). SetCachedMethod() lancera, appliquera et effacera pas. J'ai donc essentiellement une copie d'objet partielle au lieu d'une copie complète. Merci! – Geeho

+0

@Geeho: Peut-être que vous pouvez rendre le cache un objet externe, ou lier l'appel de clear-cache à un gardien (recherchez [RAII] (http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)). De cette façon, vous pouvez toujours éviter d'avoir un bloc try ... catch dans votre code. –

+0

Pas du tout une mauvaise idée. Regardera cette première chose demain matin. – Geeho

1

Si une garantie d'exception forte est importante pour MacroMethod(), je redessinerais UnsetMethod() pour ne rien lancer du tout, si vous le pouvez. Bien sûr, comment cela peut être fait dépend de ce que vous faites.

Vous utilisez UnsetMethod() pour nettoyer après une défaillance de SetMethod(). Si UnsetMethod() ne parvient pas à nettoyer, que pouvez-vous faire à ce sujet? C'est la même raison pour laquelle jeter des exceptions aux destructeurs est extrêmement dangereux.

1
  1. Non, vous ne pouvez pas en général avec le code que vous avez donné. Toutefois, en fonction de votre problème, vous pouvez peut-être effectuer une copie des données, exécuter SetMethod sur cette copie, puis inverser la représentation. Cela fournit une garantie forte, mais encore dépend du problème.
  2. Vous pouvez documenter: Forte garantie si UnsetMethod ne lance pas, basique sinon. En fait, cela explique pourquoi il est dit que les destructeurs ne devraient pas jeter. En fait toutes les opérations d'annulation ne devraient pas jeter.
  3. Oui, voir 1.
  4. Non, cela n'a aucun sens.
0

Au fond, ce que vous devez faire dans les fonctions qui font appel lancer des fonctions est le travail sur une copie temporaire des données jusqu'à ce que toutes les sous-fonctions éventuellement de Lanceurs ont fini, puis swap (qui ne doit pas jeter) le temporaire valeurs dans la structure «réelle».

Certains psudocode illustrative:

void MacroMethod() throw(...) 
{ 
    int i = 0; 
    MyValues working_copy[100] = *this; //obviously this is psudocode as I don't know your real code 
    for(i = 0; i < 100; ++i) 
    working_vopy[i].SetMethod(i); // if this throws the exception will propogate out, and no changes will be made 
    swap(*this, working_copy); // this must not throw 
}