Question

Utilisant Java depuis plusieurs années, nous n’avons pas suivi C ++. La Finalement clause a-t-elle été ajoutée à la gestion des exceptions C ++ dans la définition du langage?

Existe-t-il un idiome de prédilection qui imite les tentatives / finales de Java?

Je suis également préoccupé par le fait que C ++ n'a pas de super type ultime pour toutes les exceptions possibles pouvant être émises, comme la classe Throwable de Java.

Je peux écrire:

try {
  // do something
} catch(...) {
  // alas, can't examine the exception
  // can only do cleanup code and perhaps rethrow, ala:
  throw;
}

ADDENDUM EDIT:

  

J'ai fini par accepter la réponse   eu le plus de votes, c’est-à-dire utiliser   destructeurs à faire le nettoyage. Bien sûr,   de mes propres commentaires, il est clair que je   Je ne suis pas tout à fait d'accord avec ça.   Cependant, C ++ est ce qu'il est et ainsi   les efforts d'application que j'ai   l'esprit, je vais plus ou moins lutter   adhérer à la communauté commune   entraine toi. Je vais utiliser des classes de modèle pour   envelopper les ressources qui n'ont pas déjà   un destructeur de classe (bibliothèque C)   ressources), leur conférant ainsi   sémantique des destructeurs.

NOUVEAU MODIFICATION D'ADDENDA:

  

Hmm, au lieu de enfin puis d'une fermeture   fonctionnalité peut-être? Une fermeture combinée avec   Approche ScopeGuard (voir l’un des   réponses ci-dessous) serait un moyen de   accomplir le nettoyage avec arbitraire   actions et accès au nettoyage   contexte de portée externe du code. Le nettoyage peut s'effectuer de la même manière que dans la programmation Ruby, où ils fournissent des blocs de nettoyage lors de l'ouverture d'une ressource. N'est pas un   fonction de fermeture envisagée pour   C ++?

Était-ce utile?

La solution

En utilisant efficacement les destructeurs. Quand une exception est levée dans un bloc try, tout objet créé dans ce bloc sera immédiatement détruit (et par conséquent son destructeur appelé).

Ceci est différent de Java où vous ne savez pas quand le finaliseur d'un objet sera appelé.

MISE À JOUR : directement de la bouche du cheval: Pourquoi le C ++ ne fournit-il pas un & Quotient finally & Quot; construire?

Autres conseils

Mon 0,02 $. Je programme depuis des années dans des langages gérés tels que C # et Java, mais je suis obligé de passer au C ++ pour des raisons de rapidité. Au début, je ne pouvais pas croire que je devais écrire deux fois la signature de la méthode dans le fichier d’en-tête, puis dans le fichier cpp. Je n’aimais pas le fait qu’il n’y avait pas de blocage final, et qu’aucune collecte ne permettait de suivre les fuites de mémoire partout - ça alors je n'ai pas du tout aimé!

Cependant, comme je l’ai dit, j’ai été forcé d’utiliser C ++. J'ai donc été obligé de l'apprendre sérieusement et maintenant j'ai enfin compris tous les idiomes de la programmation comme RAII et j'ai toutes les subtilités de la langue, etc. Cela m'a pris un certain temps, mais je vois maintenant à quel point un langage est différent de celui de C # ou de Java.

Ces jours-ci, je pense que le C ++ est le meilleur langage qui existe! Oui, je peux comprendre qu’il ya parfois un peu plus de ce que j’appelle «paillettes» (choses apparemment inutiles à écrire), mais après avoir utilisé la langue sérieusement, j’ai complètement changé d’avis à ce sujet.

J'avais des fuites de mémoire tout le temps. J'avais l'habitude d'écrire tout mon code dans le fichier .h parce que je détestais la séparation du code, je ne comprenais pas pourquoi ils le feraient! Et j’avais toujours l'habitude de me retrouver avec des dépendances cycliques stupides, et plus encore. J'étais vraiment accro à C # ou Java, le C ++ était pour moi un énorme pas en avant. Ces jours je l'obtiens. Je n'ai presque jamais de fuites de mémoire, j'apprécie la séparation de l'interface et de la mise en œuvre et je n'ai plus aucun problème avec les dépendances de cycle.

Et je ne manque pas non plus le blocage final. Pour être honnête, mon opinion est que ces programmeurs C ++ dont vous parlez qui écrivent des actions de nettoyage répétées dans des blocs catch me semblent tout simplement être de mauvais programmeurs C ++. Je veux dire, il ne semble pas que les autres programmeurs C ++ de ce fil aient l'un des problèmes que vous avez mentionnés. RAII rend vraiment finalement redondant, et si c'est quelque chose, c'est moins de travail. Vous écrivez un destructeur et vous n'avez jamais à en écrire un autre enfin! Bien au moins pour ce type.

Sauf votre respect, je pense que vous êtes maintenant habitué à Java, tout comme moi.

La réponse de C ++ est RAII: le destructeur de l'objet sera exécuté lorsqu'il sortira de sa portée. Que ce soit par retour, par une exception ou autre. Si vous gérez l'exception quelque part ailleurs, vous pouvez être sûr que tous les objets de la fonction appelée à votre gestionnaire seront correctement détruits si leur destructeur est appelé. Ils vont nettoyer pour vous.

Lire http://en.wikipedia.org/wiki/Resource_acquisition_is_initialization

Non, finalement, il n'a pas été ajouté à C ++, et il est peu probable qu'il soit ajouté.

La manière dont C ++ utilise le constructeur / destructeur rend le besoin de finalement inutile.
Si vous utilisez catch (...) pour nettoyer, vous n'utilisez pas C ++ correctement. Le code de nettoyage doit tous figurer dans le destructeur.

Bien que son utilisation ne soit pas obligatoire, C ++ a une exception std :: exception.
Forcer les développeurs à dériver d'une classe spécifique à utiliser une exception va à l'encontre de la philosophie du maintien simple du C ++. C’est aussi pourquoi nous n’exigeons pas que toutes les classes dérivent de Object.

Lire: C ++ prend-il en charge les blocs 'finalement'? (Et de quoi parle-t-on encore cette 'RAII'?

L’utilisation de finally est plus propice aux erreurs que les destructeurs à effectuer le nettoyage.
En effet, vous obligez l'utilisateur de l'objet à effectuer un nettoyage plutôt que le concepteur / implémenteur de la classe.

D'accord, je dois ajouter une réponse aux points que vous avez soulevés dans un message séparé: (Ce serait beaucoup plus pratique si vous aviez modifié cette question dans la question initiale, afin qu'elle ne se retrouve pas au bas de la liste ci-dessous .

  

Si tout le nettoyage est toujours effectué dans   destructeurs alors il n'y aurait pas besoin   être un code de nettoyage dans une prise   block - pourtant C ++ a des blocs de capture où   les actions de nettoyage se font. En effet il   a un bloc pour attraper (...) où il se trouve   seulement possible de faire des actions de nettoyage   (eh bien, je ne peux certainement pas en trouver   informations d'exception pour faire tout   enregistrement).

catch a un objectif complètement différent, et en tant que programmeur Java, vous devez en être conscient. La clause finally est pour & "Inconditionnel &"; actions de nettoyage. Peu importe la façon dont le bloc est quitté, cela doit être fait. Catch est pour le nettoyage conditionnel. Si ce type d’exception est levé, nous devons effectuer quelques actions supplémentaires.

  

Le nettoyage dans un bloc finally sera   se faire s'il y avait un   exception levée ou non - ce qui est   ce qu'on veut toujours arriver quand   Le code de nettoyage existe.

vraiment? Si nous voulons que cela soit toujours appliqué à ce type (par exemple, nous voulons toujours fermer une connexion à une base de données lorsque nous en avons terminé avec elle), alors pourquoi ne pas la définir une fois ? Dans le type lui-même? Assurez-vous que la connexion à la base de données se ferme d'elle-même, plutôt que d'avoir à essayer / enfin à chaque utilisation?

C'est le but des destructeurs. Ils garantissent que chaque type est en mesure de prendre en charge son propre nettoyage, à chaque fois qu’il est utilisé, sans que l’appelant n’y pense.

  

Les développeurs C ++ depuis le premier jour ont été   en proie à avoir à répéter le nettoyage   actions qui apparaissent dans des blocs catch dans   le flux de code qui se produit sur   sortie réussie du bloc try.   Les programmeurs Java et C # le font juste   une fois dans le bloc enfin.

Non. Les programmeurs C ++ n'ont jamais été affectés par cela. C programmeurs ont. Et les programmeurs C qui ont compris que c ++ avait des classes, puis se sont appelés eux-mêmes les programmeurs C ++.

Je programme tous les jours en C ++ et en C #, et j’ai l’impression que je suis harcelé par la ridicule insistance de C # selon laquelle je dois fournir une clause finally (ou un bloc using) CHAQUE TEMPS J’utilise une connexion à une base de données ou autre chose qui doit être nettoyé.

C ++ me permet de spécifier une fois pour toutes que & "; chaque fois que nous en avons terminé avec ce type, il devrait effectuer ces actions &"; Je ne risque pas d'oublier de libérer de la mémoire. Je ne risque pas d'oublier de fermer des descripteurs de fichiers, des sockets ou des connexions de base de données. Parce que ma mémoire, mes poignées, mes sockets et mes connexions à la base de données le font eux-mêmes.

Comment peut-il jamais être obligé d'écrire un code de nettoyage en double chaque fois que vous utilisez un type? Si vous devez envelopper le type car il ne possède pas de destructeur, vous avez deux options simples:

  • Recherchez une bibliothèque C ++ appropriée fournissant ce destructeur (indice: Boost)
  • Utilisez boost :: shared_ptr pour l'envelopper et fournissez-lui un foncteur personnalisé au moment de l'exécution, en spécifiant le nettoyage à effectuer.
  

Lorsque vous écrivez un serveur d'application   des logiciels tels que les serveurs d'applications Java EE   Glassfish, JBoss, etc., vous voulez être   capable d'attraper et de connecter une exception   information - par opposition à laisser   tomber par terre. Ou pire tomber dans   le temps d'exécution et causer un ingrat   sortie brusque du serveur d'applications.   C'est pourquoi il est très souhaitable d'avoir   une classe de base globale pour tout   exception possible.   Et C ++ a une telle classe. std :: exception.

     

Avoir utilisé le C ++ depuis le jour CFront   et Java / C # pendant la majeure partie de cette décennie. Est   clair pour voir il y a juste un énorme   écart de culture dans la façon dont fondamentalement   des choses similaires sont abordées.

Non, vous n’avez jamais utilisé le C ++. Vous avez fait CFront ou C avecDes classes. Pas C ++. Il y a une énorme différence. Arrêtez d'appeler les réponses boiteuses et vous pourriez apprendre quelque chose sur la langue que vous pensiez connaître. ;)

Les fonctions de nettoyage, elles-mêmes, sont complètement boiteuses. Ils ont une faible cohésion, en ce sens qu'ils sont censés effectuer une série d'activités uniquement liées au moment où elles se produisent. Ils ont un couplage élevé, en ce sens qu'ils doivent faire modifier leurs internes lorsque les fonctions qui font réellement quelque chose sont modifiées. Pour cette raison, ils sont sujets aux erreurs.

La construction try ... finally est un framework pour les fonctions de nettoyage. C'est une manière encouragée par la langue pour écrire du code moche. De plus, dans la mesure où il encourage à écrire le même code de nettoyage à plusieurs reprises, il sape le principe DRY.

La méthode C ++ est de loin préférable à ces fins. Le code de nettoyage d'une ressource est écrit précisément une fois dans le destructeur. Il se trouve au même endroit que le reste du code pour cette ressource et présente donc une bonne cohésion. Le code de nettoyage ne doit pas nécessairement être inséré dans des modules indépendants, ce qui réduit le couplage. Il est écrit précisément une fois, quand il est bien conçu.

De plus, la méthode C ++ est beaucoup plus uniforme. C ++, avec les ajouts du pointeur intelligent, gère toutes sortes de ressources de la même manière, tandis que Java gère bien la mémoire et fournit des constructions inadéquates pour libérer d'autres ressources.

Il y a beaucoup de problèmes avec C ++, mais ce n'est pas l'un d'entre eux. Java peut être meilleur que le C ++, mais ce n’est pas le cas.

Java serait bien mieux avec un moyen d’implémenter RAII au lieu d’essayer ... enfin.

Pour éviter de devoir définir une classe wrapper pour chaque ressource libérable, ScopeGuard ( http : //www.ddj.com/cpp/184403758 ) qui permet de créer " nettoyeurs " à la volée.

Par exemple:

FILE* fp = SomeExternalFunction();
// Will automatically call fclose(fp) when going out of scope
ScopeGuard file_guard = MakeGuard(fclose, fp);

Un exemple de la difficulté d'utiliser enfin correctement.

Ouverture et fermeture de deux fichiers.
Où vous voulez garantir que le fichier est fermé correctement.
Attendre le CPG n’est pas une option, les fichiers peuvent être réutilisés.

En C ++

void foo()
{
    std::ifstream    data("plop");
    std::ofstream    output("plep");

    // DO STUFF
    // Files closed auto-magically
}

Dans un langage dépourvu de destructeurs mais avec une clause finally.

void foo()
{
    File            data("plop");
    File            output("plep");

    try
    {
        // DO STUFF
    }
    finally
    {
        // Must guarantee that both files are closed.
        try {data.close();}  catch(Throwable e){/*Ignore*/}
        try {output.close();}catch(Throwable e){/*Ignore*/}
    }
}

Ceci est un exemple simple et déjà le code devient compliqué. Ici, nous essayons seulement de rassembler 2 ressources simples. Mais à mesure que le nombre de ressources à gérer augmente et / ou que leur complexité augmente, l’utilisation d’un bloc enfin devient de plus en plus difficile à utiliser correctement en présence d’exceptions.

L'utilisation de finalement déplace la responsabilité de l'utilisation correcte sur l'utilisateur d'un objet. En utilisant le mécanisme constructeur / destructeur fourni par C ++, vous déplacez la responsabilité de l'utilisation correcte vers le concepteur / implémenteur de la classe. C’est une sécurité en soi, car le concepteur n’a besoin de le faire correctement qu’une fois au niveau de la classe (au lieu de demander à différents utilisateurs de le faire de différentes manières).

Utilisation de C ++ 11 avec ses expressions lambda , j'ai récemment commencé à utiliser le code suivant pour imiter finally:

class FinallyGuard {
private:
  std::function<void()> f_;
public:
  FinallyGuard(std::function<void()> f) : f_(f) { }
  ~FinallyGuard() { f_(); }
};

void foo() {
  // Code before the try/finally goes here
  { // Open a new scope for the try/finally
    FinallyGuard signalEndGuard([&]{
      // Code for the finally block goes here
    });
    // Code for the try block goes here
  } // End scope, will call destructor of FinallyGuard
  // Code after the try/finally goes here
}

Le FinallyGuard est un objet construit avec un argument semblable à une fonction appelable, de préférence une expression lambda. Il se souviendra simplement de cette fonction jusqu'à l'appel de son destructeur, ce qui est le cas lorsque l'objet sort de la portée, soit en raison d'un flux de contrôle normal, soit en raison du déroulement de la pile lors de la gestion des exceptions. Dans les deux cas, le destructeur appellera la fonction, exécutant ainsi le code en question.

Il est un peu étrange que vous deviez écrire le code pour le try avant le code pour le bloc std::function<void()>, mais à part cela, cela ressemble beaucoup à un véritable < => / <=> de Java. J'imagine qu'il ne faut pas en abuser dans les situations où un objet doté de son propre destructeur serait plus approprié, mais dans certains cas, j'estime que cette approche est plus appropriée. J'ai abordé l'un de ces scénarios dans cette question .

Pour autant que je sache, <=> utilisera une indirection de pointeur et au moins un appel de fonction virtuelle pour effectuer son type effacement. , il y aura donc un ralentissement des performances . N'utilisez pas cette technique dans une boucle étroite où les performances sont essentielles. Dans ces cas, un objet spécialisé dont le destructeur ne fait qu'une chose serait plus approprié.

Les destructeurs C ++ rendent finally redondant. Vous pouvez obtenir le même effet en déplaçant le code de nettoyage de finalement vers les destructeurs correspondants.

Je pense que vous manquez le but de ce que catch (...) peut faire.

Vous dites dans votre exemple & "; hélas, vous ne pouvez pas examiner l'exception &" ;. Eh bien, vous n'avez aucune information sur le type de l'exception. Vous ne savez même pas s'il s'agit d'un type polymorphe. Même si vous aviez une référence non typée, vous ne pourriez même pas tenter une dynamic_cast.

en toute sécurité.

Si vous connaissez certaines exceptions ou hiérarchies d'exceptions avec lesquelles vous pouvez faire quelque chose, c'est l'endroit idéal pour les blocs catch avec des types nommément explicites.

try n'est pas souvent utile en C ++. Il peut être utilisé dans des endroits qui doivent garantir qu'ils ne lancent pas, ou ne lancent que certaines exceptions contractées. Si vous utilisez catch pour le nettoyage, il y a de fortes chances que votre code ne soit absolument pas sûr en toute exception.

Comme mentionné dans d'autres réponses, si vous utilisez des objets locaux pour gérer des ressources (RAII), il peut être surprenant et instructif de constater le nombre limité de blocages dont vous avez besoin, souvent - si vous n'avez rien à faire localement avec une exception - même le bloc try peut être redondant car vous laissez les exceptions s'écouler dans le code client qui peut y répondre tout en garantissant l'absence de problèmes de ressources.

Pour répondre à votre question initiale, si vous avez besoin d'un morceau de code à exécuter à la fin d'un bloc, d'une exception ou d'aucune exception, alors une recette serait.

class LocalFinallyReplacement {
    ~LocalFinallyReplacement() { /* Finally code goes here */ }
};
// ...
{ // some function...
    LocalFinallyReplacement lfr; // must be a named object

    // do something
}

Remarquez comment nous pouvons complètement supprimer throw, <=> et <=>.

Si vous aviez des données dans la fonction qui avaient été initialement déclarées en dehors du bloc try, vous avez besoin d'un accès dans & "finally &"; Dans ce cas, vous devrez peut-être ajouter cela au constructeur de la classe d'assistance et le stocker jusqu'au destructeur. Cependant, à ce stade, je voudrais sérieusement réfléchir à la possibilité de résoudre le problème en modifiant la conception des objets de traitement des ressources locales, car cela impliquerait un problème dans la conception.

Pas complètement hors sujet.

Nettoyage des ressources de la base de données Boiler Plating dans Java

Mode sarcasme: le langage Java n'est-il pas merveilleux?

Au cours de ces 15 années, j'ai beaucoup travaillé sur la conception de classes et de wrapper de modèles en C ++, à la manière du C ++ en termes de nettoyage des destructeurs. Cependant, chaque projet impliquait aussi invariablement l’utilisation de bibliothèques C qui fournissaient des ressources avec l’ouvert open it, use it, close it model d’utilisation. Un essai / enfin signifierait qu'une telle ressource peut simplement être consommée là où elle doit être - d'une manière tout à fait robuste - et en être complétée. L’approche la moins fastidieuse pour programmer cette situation. Pourrait traiter de tous les autres états en cours pendant la logique de ce nettoyage sans avoir à se retrouver dans un destructeur de wrapper.

J’ai réalisé la plupart de mes codages C ++ sous Windows, donc j’ai toujours pu utiliser le __try / __ de Microsoft pour de telles situations. (Leur gestion structurée des exceptions possède de puissantes capacités pour interagir avec les exceptions.) Hélas, le langage C ne semble pas avoir ratifié les constructions portables de gestion des exceptions.

Cependant, ce n’était pas la solution idéale, car il n’était pas simple de mélanger du code C et C ++ dans un bloc try où un style d’exception pouvait être levé. Un dernier bloc ajouté à C ++ aurait été utile dans ces situations et aurait permis la portabilité.

En ce qui concerne votre addendum-edit, oui, des fermetures sont envisagées pour C ++ 0x. Ils peuvent être utilisés avec les gardes de portée RAII pour fournir une solution facile à utiliser, consultez Le blog de Pizer . Ils peuvent aussi être utilisés pour imiter try-finally, voir cette réponse ; mais est-ce vraiment une bonne idée? .

Je pensais y ajouter ma propre solution: une sorte de wrapper de pointeur intelligent pour traiter les types non RAII.

Utilisé comme ceci:

Finaliser< IMAPITable, Releaser > contentsTable;
// now contentsTable can be used as if it were of type IMAPITable*,
// but will be automatically released when it goes out of scope.

Alors, voici l'implémentation de Finaliser:

/*  Finaliser
    Wrap an object and run some action on it when it runs out of scope.
    (A kind of 'finally.')
    * T: type of wrapped object.
    * R: type of a 'releaser' (class providing static void release( T* object )). */
template< class T, class R >
class Finaliser
{
private:
    T* object_;

public:
    explicit Finaliser( T* object = NULL )
    {
        object_ = object;
    }

    ~Finaliser() throw()
    {
        release();
    }

    Finaliser< T, R >& operator=( T* object )
    {
        if (object_ != object && object_ != NULL)
        {
            release();
        }
        object_ = object;

        return *this;
    }

    T* operator->() const
    {
        return object_;
    }

    T** operator&()
    {
        return &object_;
    }

    operator T*()
    {
        return object_;
    }

private:
    void release() throw()
    {
        R::release< T >( object_ );
    }
};

... et voici Releaser:

/*  Releaser
    Calls Release() on the object (for use with Finaliser). */
class Releaser
{
public:
    template< class T > static void release( T* object )
    {
        if (object != NULL)
        {
            object->Release();
        }
    }
};

J'ai plusieurs types de déclencheurs comme celui-ci, y compris un pour free () et un pour CloseHandle ().

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