User Tools

Site Tools


cpp:syntax:exceptions

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
cpp:syntax:exceptions [2021/04/11 10:06]
bmonsuez
cpp:syntax:exceptions [2022/11/18 10:48] (current)
Line 70: Line 70:
  
 C'est ce mécanisme que l'on appelle //support des exceptions//​. C'est ce mécanisme que l'on appelle //support des exceptions//​.
 +
 +===== La syntaxe des exceptions en C++ =====
 +
 +C++ ajoute le mot clé ''​throw''​ pour déclencher une exception. En fait à une exception est associée une valeur ou un objet quelconque.
 +
 +<code cpp>
 +  int divrem(int numerator, int denominator,​ int& remainder)
 +  {
 +      if(denominator == 0)
 +        throw 0;
 +      remainder = numerator % denominator;​
 +      return numerator / denominator;​
 +  }
 +</​code>​
 +
 +Dans le code précédent,​ si le dénominateur est égal à ''​0'',​ une exception sera levée et à cette exception sera associée un entier ayant la valeur ''​0''​.
 +
 +Quand l'​exception est levée, l'​exécution du programme s'​arrête tant que l'​exécution n'est pas capturée. Si l'​exécution n'est pas capturée, le programme //plante//, ie. il s'​arrête et indique le type de l'​objet associé à l'​exécution. ​
 +
 +Il est donc nécessaire d'​introduire un mécanisme permettrant de capturer les exceptions :
 +
 +<code cpp>
 +  try
 +  {
 +      divrem(2, 0);
 +  }
 +  catch(...)
 +  {
 +      std::cout << "​Error:​ Try to divide by zero" << std::endl;
 +  }
 +</​code>​
 +
 +
 +Le bloc ''​try{}catch(...){}''​ fonctionne comme suit :
 +  * toute exception se produisant entre les deux accolades du bloc ''​try{}''​ est capturée par le bloc ''​catch(...)''​ et le code entre les deux accolades du bloc ''​catch(...){}''​ est exécuté.
 +
 +Dans le cas précédent,​ la sortie du programme serait l'​affichage du message :
 +
 +  Error: Try to divide by zero
 +  ​
 +===== Distinguer entre les exceptions =====
 +
 +En fait, nous aimerons identifier la nature de l'​erreur qui s'est produite. Pour ce faire, nous aimerions par exemple faire la différence entre une erreur d'​entrée sortie si nous lisons à partir d'un flux et une erreur de calcul.
 +
 +C++ proposer un mécanisme pour faire la différence entre les exceptions en permettant de ne capturer que les exceptions qui ont un certain type :
 +
 +<code cpp>
 +  try
 +  {
 +      divrem(2, 0);
 +  }
 +  catch(std::​invalid_argument)
 +  {
 +      std::cout << "​Error:​ Invalid argument when calling the function."​ << std::endl;
 +  }
 +  catch(...)
 +  {
 +      std::cout << "​Error:​ Unknwon error when calling the function."​ << std::endl;
 +  }
 +</​code>​
 +
 +exécutera le code :
 +
 +<code cpp>
 +  catch(std::​invalid_argument)
 +  {
 +      std::cout << "​Error:​ Invalid argument when calling the function."​ << std::endl;
 +  }
 +</​code>​
 +
 +si une exception de type ''​std::​invalid_argument''​ s'est produite. Si une autre exception s'est produite, il se rabattra sur la règle par défaut, ​
 +<code cpp>
 +  catch(...)
 +  {
 +      std::cout << "​Error:​ Unknwon error when calling the function."​ << std::endl;
 +  }
 +</​code>​
 +
 +Nous voyons donc l'​intérêt de définir des objets ayant un type indiquant la nature de exception et non pas d'​associer à une expression une valeur entière ou autre, afin de pouvoir identifier la nature de l'​exception et faciliter ainsi la mise en place de code pour traiter l'​exception.
 +
 +===== Récupérer des informations complémentaires =====
 +
 +Nous modifions désormais la fonction ''​divrem''​ pour que celle-ci génère une exception ''​std::​invalid_argument''​ si 
 +jamais ''​denominator''​ vaut zero :
 +
 +<code cpp>
 +
 +#​include<​stdexcept>​
 +
 +int divrem(int numerator, int denominator,​ int& remainder)
 +{
 +    if(denominator == 0)
 +      throw std::​invalid_argument();​
 +    remainder = numerator % denominator;​
 +    return numerator / denominator;​
 +}
 +</​code>​
 +
 +Lorsque nous exécutons le code suivant :
 +<code cpp>
 +  try
 +  {
 +      divrem(2, 0);
 +  }
 +  catch(std::​invalid_argument)
 +  {
 +      std::cout << "​Error:​ Invalid argument when calling the function."​ << std::endl;
 +  }
 +  catch(...)
 +  {
 +      std::cout << "​Error:​ Unknwon error when calling the function."​ << std::endl;
 +  }
 +</​code>​
 +
 +le message suivant sera affiché :
 +
 +  Error: Invalid argument when calling the function.
 +  ​
 +Cependant, nous aimerions avoir un peu plus d'​information sur l'​erreur. Nous savons uniquement qu'une valeur passée en argument est incorrecte.
 +
 +Pour ce faire, nous pouvons ajouter des informations lors de la création de l'​objet [[https://​en.cppreference.com/​w/​cpp/​error/​invalid_argument|''​std::​invalid_argument''​]].
 +
 +
 +<code cpp>
 +
 +#​include<​stdexcept>​
 +
 +int divrem(int numerator, int denominator,​ int& remainder)
 +{
 +    if(denominator == 0)
 +      throw std::​invalid_argument("​denominator is zero"​);​
 +    remainder = numerator % denominator;​
 +    return numerator / denominator;​
 +}
 +
 +...
 +  try
 +  {
 +      divrem(2, 0);
 +  }
 +  catch(std::​invalid_argument e)
 +  {
 +      std::cout << "​Error:​ " << e.what() ​ << std::endl;
 +  }
 +  catch(...)
 +  {
 +      std::cout << "​Error:​ Unknwon error when calling the function."​ << std::endl;
 +  }
 +...
 +</​code>​
 +
 +Dans ce cas, nous capturons l'​exception par la clause suivante :
 +<code cpp>
 +  catch(std::​invalid_argument e)
 +  {
 +      std::cout << "​Error:​ " << e.what() ​ << std::endl;
 +  }
 +</​code>​
 +
 +et ''​e''​ contient une copie de l'​exception. Dés lors, il est possible d'​accéder aux champs et méthodes de l'​objet ''​std::​invalid_argument''​ et notamment la méthode ''​what()''​ qui retourne le message d'​erreur qui a été passé à l'​objet lors de son initialisation.
 +
 +C'est pour cela que lorsque l'on exécute le code, nous allons obtenir le message suivant :
 +
 +  Error: denominator is zero
 +
 +Ce qui est beaucoup plus parlant que le message générique.
 +
 +===== Relancer une exception =====
 +
 +<code cpp>
 +
 +#​include<​iostream>​
 +#​include<​sstream>​
 +#​include<​string>​
 +#​include<​stdexcept>​
 +
 +std::string read_file_content(std::​string theFileName)
 +{
 +    if(theFileName.empty)
 +        throw std::​invalid_argument("​file name is empty"​);​
 +    if(!std::​filesystem.exists(theFileName))
 +        throw std::​invalid_argument("​file name does not exists"​);​
 +    ​
 +    std::​ifstream fileStream(filename,​ std::​ios::​in);​
 +    if(fileStream.fail())
 +        throw std::​invalid_argument("​file name does not exists"​);​
 +    ostringstream result;
 +    try
 +    {
 +        while(!file.eof())
 +        {
 +            char buffer[512];​
 +            int numberOfCharsRead = 512;
 +            fileStream.read(buffer,​ numberOfCharsRead);​
 +            if(fileStream.fail()) ​
 +            {
 +                if(!fileStream.eof())
 +                    throw std::​ios::​failure("​IO error when reading from file"​);​
 +                numberOfCharsRead = fileStream.gcount();​
 +            }
 +            result.write(buffer,​ numberOfCharsRead);​
 +        }
 +    }
 +    catch(...)
 +    {
 +        fileStream.close();​
 +        throw;
 +    }
 +    return result.str();​
 +}
 +</​code>​
 +
 +Ce code ouvre un fichier et lit son contenu. Il génère les exceptions suivantes :
 +
 +  * ''​std::​argument_exception''​ si le nom du fichier est vide ou si le fichier n'​existe pas.
 +  * ''​std::​ios::​failure''​ si une erreur se produit pendant la lecture du fichier.
 +
 +Cependant, nous avons placé le bloc suivant :
 +<code cpp>
 +    try
 +    {
 +        while(!file.eof())
 +        {
 +            ...
 +        }
 +    }
 +    catch(...)
 +    {
 +        fileStream.close();​
 +        throw;
 +    }
 +</​code>​
 +
 +qui va capturer si une erreur d'​entrée/​sortie va se produire. Si c'est le cas, l'​erreur va être capturée, nous allons forcer la fermeture du fichier et nous allons relancer l'​exception,​ c'est ce que fait l'​instruction ''​throw''​ sans paramètre, elle relance l'​exception qui vient d'​être capturée pour indiquer l'​erreur à la fonction appelante ((Dans le cas présent, ce code est inutile, puisque le fichier est bien fermé au moment de la destruction du fichier, mais c'est simplement pour montrer comment capturer une exception pour effectuer un traitement local et la propager à l'​appelant pour terminer le traitement)).
 +
  
  
  
cpp/syntax/exceptions.1618135572.txt.gz · Last modified: 2021/04/11 10:06 by bmonsuez