Comment lever une exception C ++

260

J'ai une très mauvaise compréhension de la gestion des exceptions (c'est-à-dire, comment personnaliser les instructions throw, try, catch à mes propres fins).

Par exemple, j'ai défini une fonction comme suit: int compare(int a, int b){...}

J'aimerais que la fonction lève une exception avec un message lorsque a ou b est négatif.

Comment dois-je aborder cela dans la définition de la fonction?

Terry Li
la source
3
Vous devriez lire ceci: gotw.ca/publications/mill22.htm .
Oliver Charlesworth
37
@OliCharlesworth, ne pensez-vous pas que c'est un peu trop à jeter sur quelqu'un qui est confus par les bases?
Mark Ransom
6
Les exceptions superflues méritent d'être évitées. Si vous ne voulez pas que votre appelant passe des valeurs négatives, rendez-le plus évident en spécifiant unsigned intcomme paramètres dans votre signature de fonction. Là encore, je suis de l'école que vous ne devriez lever et attraper des exceptions que pour des choses qui sont réellement exceptionnelles.
AJG85
1
@Mark: Au départ, j'ai mal compris la question de savoir s'il fallait utiliser throw()des spécifications d'exception sur les fonctions.
Oliver Charlesworth,

Réponses:

364

Facile:

#include <stdexcept>

int compare( int a, int b ) {
    if ( a < 0 || b < 0 ) {
        throw std::invalid_argument( "received negative value" );
    }
}

La bibliothèque standard est livrée avec une belle collection d' objets d'exception intégrés que vous pouvez lancer. Gardez à l'esprit que vous devez toujours lancer par valeur et attraper par référence:

try {
    compare( -1, 3 );
}
catch( const std::invalid_argument& e ) {
    // do stuff with exception... 
}

Vous pouvez avoir plusieurs instructions catch () après chaque essai, vous pouvez donc gérer différents types d'exceptions séparément si vous le souhaitez.

Vous pouvez également relancer les exceptions:

catch( const std::invalid_argument& e ) {
    // do something

    // let someone higher up the call stack handle it if they want
    throw;
}

Et pour intercepter les exceptions quel que soit le type:

catch( ... ) { };
nsanders
la source
26
Et vous devriez toujours attraper des exceptions en tant que const
Adrian Cornish
2
@TerryLiYifeng si les exceptions personnalisées ont plus de sens, allez-y. Vous pouvez toujours vouloir dériver de std :: exception et garder l'interface la même.
nsanders
2
+ 1'ed à nouveau mais je pense que c'est assez important - car cela met en évidence le fait qu'il s'agit d'un objet temporaire maintenant - donc la modification est inutile.
Adrian Cornish
2
@AdrianCornish: Ce n'est pas vraiment temporaire cependant. Les captures non constantes peuvent être utiles .
GManNickG
26
Vous devriez généralement lancer à nouveau avec un simple throw;(en renvoyant l'objet d'origine et en préservant son type) plutôt qu'en throw e;(jetant une copie de l'objet capturé, en changeant éventuellement son type).
Mike Seymour
17

Ajoutez simplement throwlà où vous en avez besoin et trybloquez l'appelant qui gère l'erreur. Par convention, vous ne devriez jeter que les choses qui en dérivent std::exception, donc incluez d' <stdexcept>abord.

int compare(int a, int b) {
    if (a < 0 || b < 0) {
        throw std::invalid_argument("a or b negative");
    }
}

void foo() {
    try {
        compare(-1, 0);
    } catch (const std::invalid_argument& e) {
        // ...
    }
}

Consultez également Boost.Exception .

Cat Plus Plus
la source
15

Bien que cette question soit assez ancienne et ait déjà reçu une réponse, je veux juste ajouter une note sur la façon de gérer correctement les exceptions en C ++ 11:

Utiliser std::nested_exceptionetstd::throw_with_nested

Il est décrit sur StackOverflow ici et ici , comment vous pouvez obtenir une trace arrière sur vos exceptions dans votre code sans avoir besoin d'un débogueur ou d'une journalisation encombrante, en écrivant simplement un gestionnaire d'exceptions approprié qui renverra les exceptions imbriquées.

Comme vous pouvez le faire avec n'importe quelle classe d'exception dérivée, vous pouvez ajouter beaucoup d'informations à une telle trace! Vous pouvez également jeter un oeil à mon MWE sur GitHub , où une trace ressemblerait à quelque chose comme ceci:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
GPMueller
la source
8

Vous pouvez définir un message à lancer lorsqu'une certaine erreur se produit:

throw std::invalid_argument( "received negative value" );

ou vous pouvez le définir comme ceci:

std::runtime_error greatScott("Great Scott!");          
double getEnergySync(int year) {                        
    if (year == 1955 || year == 1885) throw greatScott; 
    return 1.21e9;                                      
}                                                       

En règle générale, vous auriez un try ... catchbloc comme celui-ci:

try {
// do something that causes an exception
}catch (std::exception& e){ std::cerr << "exception: " << e.what() << std::endl; }
serup
la source
6

Je voulais AJOUTER aux autres réponses décrites ici une note supplémentaire, dans le cas des exceptions personnalisées .

Dans le cas où vous créez votre propre exception personnalisée, qui dérive de std::exception, lorsque vous interceptez "tous les types d'exceptions" possibles, vous devez toujours démarrer les catchclauses avec le type d'exception "le plus dérivé" qui peut être intercepté. Voir l'exemple (de ce qu'il ne faut PAS faire):

#include <iostream>
#include <string>

using namespace std;

class MyException : public exception
{
public:
    MyException(const string& msg) : m_msg(msg)
    {
        cout << "MyException::MyException - set m_msg to:" << m_msg << endl;
    }

   ~MyException()
   {
        cout << "MyException::~MyException" << endl;
   }

   virtual const char* what() const throw () 
   {
        cout << "MyException - what" << endl;
        return m_msg.c_str();
   }

   const string m_msg;
};

void throwDerivedException()
{
    cout << "throwDerivedException - thrown a derived exception" << endl;
    string execptionMessage("MyException thrown");
    throw (MyException(execptionMessage));
}

void illustrateDerivedExceptionCatch()
{
    cout << "illustrateDerivedExceptionsCatch - start" << endl;
    try 
    {
        throwDerivedException();
    }
    catch (const exception& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an std::exception, e.what:" << e.what() << endl;
        // some additional code due to the fact that std::exception was thrown...
    }
    catch(const MyException& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an MyException, e.what::" << e.what() << endl;
        // some additional code due to the fact that MyException was thrown...
    }

    cout << "illustrateDerivedExceptionsCatch - end" << endl;
}

int main(int argc, char** argv)
{
    cout << "main - start" << endl;
    illustrateDerivedExceptionCatch();
    cout << "main - end" << endl;
    return 0;
}

REMARQUE:

0) L'ordre correct doit être vice-versa, c'est-à-dire d'abord vous catch (const MyException& e)qui est suivi par catch (const std::exception& e).

1) Comme vous pouvez le voir, lorsque vous exécutez le programme tel quel, la première clause catch sera exécutée (ce qui est probablement ce que vous ne vouliez PAS en premier lieu).

2) Même si le type capturé dans la première clause catch est de type std::exception, la version "appropriée" de what()sera appelée - car elle est capturée par référence (changez au moins le std::exceptiontype d' argument capturé par valeur - et vous rencontrerez phénomènes de "tranchage d'objets" en action).

3) Dans le cas où "un certain code dû au fait que l'exception XXX a été levée ..." fait des choses importantes EN CE QUI CONCERNE le type d'exception, il y a un mauvais comportement de votre code ici.

4) Ceci est également pertinent si les objets capturés étaient des objets "normaux" comme: class Base{};et class Derived : public Base {}...

5) g++ 7.3.0sur Ubuntu 18.04.1 génère un avertissement qui indique le problème mentionné:

Dans la fonction 'void illustreDerivedExceptionCatch ()': item12Linux.cpp: 48: 2: avertissement: une exception de type 'MyException' sera interceptée catch (const MyException & e) ^ ~~~~

item12Linux.cpp: 43: 2: avertissement: par un gestionnaire antérieur pour la capture de "std :: exception" (const const & e) ^ ~~~~

Encore une fois , je dirai que cette réponse n'est qu'à AJOUTER aux autres réponses décrites ici (je pensais que ce point valait la peine d'être mentionné, mais je ne pouvais pas le décrire dans un commentaire).

Guy Avraham
la source