Gestion des exceptions C ++

Oct 21 2020

Je reçois une exception et j'ai besoin de votre avis

La fonction principale ci-dessous tente d'allouer de la mémoire plusieurs fois, puis lance l'exception au niveau supérieur.

#include <iostream>
#include <memory>

struct MiserlinessClass{
    char  *   pMemory;
    int       memory_len;
    const int max_size = 10;
    MiserlinessClass(int len){
        if (len>max_size){
            std::cout<<"What a lavish lifestyle! Get out of my face! \n";
            std::bad_alloc exception;
            throw exception;
        }
        pMemory = (char *)malloc(len*sizeof(char));
        memory_len = len;
    }
};


int main(int argc, char** argv){
    int len = (argc==2)? strtol(argv[1],NULL,10): 5;

    std::unique_ptr<MiserlinessClass> objPtr;

    bool allocated = false;
    const int max_cnt = 5;
    int cnt = 0;
    while (!allocated){
        try{
            std::cout<<"Trying to allocate "<<len<<" chars...\n";
            objPtr.reset(new MiserlinessClass(len));
            allocated = true;
        } catch (std::bad_alloc &e){
            len = len >> 1;
            cnt++;
            if (cnt==max_cnt){
                std::cout<<"I give up \n";
                throw  e;
            }
        }
    } 
    std::cout<< "Allocated " << objPtr->memory_len << " chars \n";  
    return 0;
}

Et voici les résultats de 3 courses différentes

1 - alloue de la mémoire dès la première tentative,

2 - essaie quelques fois et alloue les disponibles et T

3 - a échoué après N tentatives et jette une exception au niveau supérieur

---------------------------------------
$ make; ./01_exception_pointer 3 make: Nothing to be done for 'all'. Trying to allocate 3 chars... Allocated 3 chars --------------------------------------- $ make; ./01_exception_pointer 64
make: Nothing to be done for 'all'.
Trying to allocate 64 chars...
What a lavish lifestyle! Get out of my face! 
Trying to allocate 32 chars...
What a lavish lifestyle! Get out of my face! 
Trying to allocate 16 chars...
What a lavish lifestyle! Get out of my face! 
Trying to allocate 8 chars...
Allocated 8 chars 
---------------------------------------
$ make; ./01_exception_pointer 1023
make: Nothing to be done for 'all'.
Trying to allocate 1023 chars...
What a lavish lifestyle! Get out of my face! 
Trying to allocate 511 chars...
What a lavish lifestyle! Get out of my face! 
Trying to allocate 255 chars...
What a lavish lifestyle! Get out of my face! 
Trying to allocate 127 chars...
What a lavish lifestyle! Get out of my face! 
Trying to allocate 63 chars...
What a lavish lifestyle! Get out of my face! 
I give up 
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

Mes questions sont

  1. Je connais la différence formelle entre renvoyer la valeur et lever l'exception, mais je ne peux toujours pas sentir s'il est temps de paniquer et de lever une exception ou si le programme doit rester calme et simplement renvoyer le code d'erreur.

  2. Y a-t-il un moyen approprié de gérer l'exception bad_alloc?

  3. Tous les commentaires et suggestions supplémentaires :)

Réponses

3 MartinYork Oct 22 2020 at 00:56

Je connais la différence formelle entre renvoyer la valeur et lever l'exception, mais je ne peux toujours pas sentir s'il est temps de paniquer et de lever une exception ou si le programme doit rester calme et simplement renvoyer le code d'erreur.

Règles de base simples.

  • Si vous ne pouvez pas résoudre le problème localement, lancez une exception.
    Vous ne pouvez pas résoudre le problème de ne pas avoir suffisamment de mémoire localement, alors lancez une exception. Cela permettra à la pile de dérouler la mémoire et vous «pourrez» arriver à un point où cela peut être résolu (ou si ce n'est pas laisser l'application se terminer). Un exemple où il peut être arrêté est lorsque vous créez une tâche indépendante et que l'une de ces tâches échoue. Cela ne signifie pas que toutes les tâches échoueront. Consignez le fait que cette tâche a échoué, autorisez l'exception à libérer toute la mémoire utilisée, puis essayez la tâche suivante.

  • Le code d'erreur ne doit pas traverser les limites de l'interface.
    Les codes d'erreur sont excellents si vous les vérifiez. Il permet à un mécanisme simple de renvoyer les informations d'un ou deux niveaux sans compliquer le code. Donc, si vous écrivez une bibliothèque et que vous utilisez en interne des codes d'erreur, c'est bien (car vous serez bon et vérifier tous les codes d'erreur). Mais vous ne pouvez pas faire confiance aux utilisateurs de votre bibliothèque, donc si une erreur va se propager en dehors de votre bibliothèque, utilisez une exception pour forcer l'utilisateur à l'obtenir explicitement.

  • Entrée utilisateur et flux. N'utilisez pas d'exceptions. L'entrée utilisateur sera toujours sujette aux erreurs et le code qui gère l'entrée utilisateur devra avoir de nombreux contrôles de validation (si cela est fait correctement). Ils le savent. Ainsi, les opérations en continu définissent simplement le flux sur mauvais.

Est-ce un moyen approprié de gérer l'exception bad_alloc?

Laissez-le se propager en haut de votre application. Enregistrez quelque chose pour informer l'utilisateur que cela s'est produit. S'il s'agit d'une tâche indépendante, supprimez la tâche et démarrez la suivante. S'il ne s'agit que d'une partie normale de l'exécution, laissez l'application se fermer.

Tous les commentaires et suggestions supplémentaires :)

  • N'utilisez pas malloc()/ free()dans le code C ++.
  • Pour chaque nouveau, il devrait y avoir une suppression.
  • Préférez utiliser make_unique()plutôt que new(pour aider à créer / supprimer une correspondance).

Révision du code:

// Owned pointers are a bad idea.
struct MiserlinessClass{
    char  *   pMemory;          // This is an owned pointer.
                                // At lot of extra work needs to be done here
                                // You need to look up the rule of three/five

            std::bad_alloc exception;
            throw exception;

Plus facile à écrire simplement:

             throw std::bad_alloc;

Ce n'est pas une situation bad_alloc.

        if (len>max_size){
            std::cout<<"What a lavish lifestyle! Get out of my face! \n";
            std::bad_alloc exception;
            throw exception;
        }

bad_alloc signifie que le système n'a pas pu allouer de mémoire en raison de la pression de la mémoire.

Vous devez utiliser: std::range_errorle paramètre d'entrée était hors de portée.


Ne pas utiliser l' mallocutilisation newici.

        pMemory = (char *)malloc(len*sizeof(char));

        // Better:
        pMemory = new char[len];

Remarque: vous devez toujours implémenter la règle de trois ici.


Remarque: Oui, vous devriez lancer une exception ci-dessus. Vous ne souhaitez pas autoriser l'utilisateur à créer des objets non valides. Ce problème doit être corrigé avant que l'application ne soit autorisée à entrer en production, donc forcer l'échec du test unitaire avec une exception est la bonne solution.


Il n'y a pas de destructeur pour la classe MiserlinessClass. Ainsi, la mémoire allouée avec malloc()va être divulguée. Vous obligeant à manquer de mémoire plus rapidement.

Voir la règle de trois.


Remarque: cela libérera l'objet précédent. Mais seulement si le nouvel objet est créé avec succès. Vous avez donc beaucoup de mémoire allouée. Ensuite, vous essayez d'allouer deux fois plus. Si cette allocation fonctionne, vous libérez l'ancienne mémoire.

            objPtr.reset(new MiserlinessClass(len));

Je proposerais la déclaration:

   std::unique_ptr<MiserlinessClass> objPtr;

dans le bloc try, faites-y l'allocation.

    try{
        std::cout<<"Trying to allocate "<<len<<" chars...\n";
        std::unique_ptr<MiserlinessClass> objPtr = std::make_unique<MiserlinessClass>(len);
        allocated = true;
    } catch (std::bad_alloc &e){
    ...

Maintenant. La mémoire est allouée puis libérée à la fin de l'essai. La prochaine fois dans la boucle, vous saurez que vous avez un magasin propre à allouer, car tout a été nettoyé depuis votre dernière tentative.

Vous pouvez aller plus loin et supprimer simplement le fichier unique_ptr.

    try{
        std::cout<<"Trying to allocate "<<len<<" chars...\n";
        MiserlinessClass       objPtr(len);
        allocated = true;
    } catch (std::bad_alloc &e){
    ...

Pourquoi ne pas simplement multiplier par 2.

            len = len >> 1;

Ou est-ce diviser par 2. Dans les deux cas, l'intention n'est pas claire. Utilisez un code qui exprime clairement votre intention.


N'imprime que s'il n'y a pas d'exception.

    std::cout<< "Allocated " << objPtr->memory_len << " chars \n";