Question

Puisqu'il n'y a pas de finalement dans C ++, vous devez utiliser le modèle de conception RAII à la place si vous souhaitez que votre code soit protégé contre les exceptions. Une façon de faire est d’utiliser le destructeur d’une classe locale comme ceci:

void foo() {
    struct Finally {
        ~Finally() { /* cleanup code */ }
    } finalizer();
    // ...code that might throw an exception...
}

C'est un gros avantage par rapport à la solution simple, car vous n'avez pas à écrire le code de nettoyage 2 fois:

try {
    // ...code that might throw an exception...
    // cleanup code (no exception)
} catch (...) {
    // cleanup code (exception)
    throw;
}

Un inconvénient majeur de la solution de classe locale est que vous ne pouvez pas accéder directement aux variables locales dans votre code de nettoyage. Donc, votre code encombrera beaucoup si vous avez besoin d'y accéder, peu importe:

void foo() {
    Task* task;
    while (task = nextTask()) {
        task->status = running;
        struct Finally {
            Task* task;
            Finally(Task* task) : task(task) {}
            ~Finally() { task->status = idle; }
        } finalizer(task);
        // ...code that might throw an exception...
    }
}

Ma question est donc la suivante: Existe-t-il une solution combinant ces deux avantages? Ainsi, a) vous n'avez pas besoin d'écrire du code en double et b) pouvez accéder aux variables locales dans le code de nettoyage, comme task dans le dernier exemple, mais sans ce type de code.

Était-ce utile?

La solution

Au lieu de définir struct Enfin , vous pouvez extraire votre code de nettoyage dans une fonction de la classe Task et utiliser le ScopeGuard .

ScopeGuard guard = MakeGuard(&Task::cleanup, task);

Voir aussi cet article de DrDobb et ceci autre article pour plus d'informations sur ScopeGuards.

Autres conseils

Je ne pense pas qu'il existe un moyen plus propre de réaliser ce que vous essayez de faire, mais je pense que le principal problème de l'approche "finally" dans votre exemple est un problème inapproprié séparation des problèmes .

E.g. la fonction foo () est responsable de la cohérence de l’objet Task, c’est rarement une bonne idée, les méthodes de Task doivent elles-mêmes être responsables de la définition du statut par quelque chose de raisonnable.

Je réalise que parfois, il existe un réel besoin d’enfin, et votre code n’est évidemment qu’un simple exemple pour montrer un point, mais ces cas sont rares. Et un code un peu plus artificiel dans de rares cas est acceptable pour moi.

Ce que j'essaie de dire, c'est que vous devriez rarement avoir besoin de constructions finales, et dans les quelques cas où vous le faites, je dirais que ne perdez pas de temps à construire une manière plus agréable. Cela ne fera que vous encourager à utiliser finalement plus que ce que vous devriez vraiment ...

C’est une façon si laide de le faire: (venez-vous de Java?)

Lisez cet article:
Le support C ++ est-il "enfin" des blocs? (Et de quoi parle-t-on encore cette 'RAII'?

Cela explique pourquoi le concept est finalement si laid et pourquoi RIAA est beaucoup plus élégante.

J'utilise normalement quelque chose de plus semblable à ceci:

class Runner {
private:
  Task & task;
  State oldstate;
public:
  Runner (Task &t, State newstate) : task(t), oldstate(t.status); 
  {
    task.status = newstate;
  };

  ~Runner() 
  {
    task.status = oldstate;
  };
};

void foo() 
{
  Task* task;
  while (task = nextTask())
  {
    Runner r(*task, running);
            // ...code that might throw an exception...
  }
}

Comme d’autres l’ont dit, la "solution" est meilleure séparation des préoccupations. Dans votre cas, pourquoi la variable de tâche ne prend-elle pas soin de se nettoyer elle-même? Si un nettoyage doit être effectué dessus, il ne devrait pas s'agir d'un pointeur, mais d'un objet RAII.

void foo() {
//    Task* task;
ScopedTask task; // Some type which internally stores a Task*, but also contains a destructor for RAII cleanup
    while (task = nextTask()) {
        task->status = running;
        // ...code that might throw an exception...
    }
}

Les pointeurs intelligents peuvent être ce dont vous avez besoin dans ce cas (boost :: shared_ptr le supprimera par défaut, mais vous pouvez spécifier des fonctions de suppression personnalisées, qui peuvent effectuer des opérations de nettoyage arbitraires à la place. Pour RAII sur les pointeurs, c vous voudrez.

Le problème n’est pas l’absence de mot-clé finally, c’est que vous utilisez des pointeurs bruts, qui ne peuvent pas implémenter RAII.

Mais en général, chaque type devrait savoir comment se nettoyer après lui-même. Pas après chaque objet qui était dans la portée lorsque l'exception a été levée (ce qui est finalement ce que vous faites et ce que vous essayez de faire), juste après lui-même. Et si chaque objet fait cela, vous n’avez pas besoin du grand fourre-tout "nettoyer après chaque objet dans la portée". fonctionner du tout.

Je suis arrivé au C ++ à partir de Delphi pour que je sache de quoi je parle. Je déteste enfin !!! C'est laid . Je ne pense vraiment pas que le C ++ manque enfin.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top