Recherche…


Catching exceptions

Un bloc try/catch est utilisé pour intercepter des exceptions. Le code dans la section try est le code qui peut générer une exception et le code dans la ou catch clauses catch gère l'exception.

#include <iostream>
#include <string>
#include <stdexcept>

int main() {
  std::string str("foo");
  
  try {
      str.at(10); // access element, may throw std::out_of_range
  } catch (const std::out_of_range& e) {
      // what() is inherited from std::exception and contains an explanatory message
      std::cout << e.what();
  }
}

Plusieurs clauses catch peuvent être utilisées pour gérer plusieurs types d'exceptions. Si plusieurs clauses catch sont présentes, le mécanisme de gestion des exceptions tente de les faire correspondre dans l'ordre d'apparition dans le code:

std::string str("foo");
  
try {
    str.reserve(2); // reserve extra capacity, may throw std::length_error
    str.at(10); // access element, may throw std::out_of_range
} catch (const std::length_error& e) {
    std::cout << e.what();
} catch (const std::out_of_range& e) {
    std::cout << e.what();
}

Les classes d'exception dérivées d'une classe de base commune peuvent être interceptées avec une seule clause catch pour la classe de base commune. L'exemple ci-dessus peut remplacer les deux clauses catch pour std::length_error et std::out_of_range avec une clause unique pour std:exception :

std::string str("foo");
  
try {
    str.reserve(2); // reserve extra capacity, may throw std::length_error
    str.at(10); // access element, may throw std::out_of_range
} catch (const std::exception& e) {
    std::cout << e.what();
}

Comme les clauses catch sont essayées dans l'ordre, veillez à écrire plus de clauses catch spécifiques, sinon votre code de gestion des exceptions risque de ne jamais être appelé:

try {
    /* Code throwing exceptions omitted. */
} catch (const std::exception& e) {
    /* Handle all exceptions of type std::exception. */
} catch (const std::runtime_error& e) {
    /* This block of code will never execute, because std::runtime_error inherits
       from std::exception, and all exceptions of type std::exception were already
       caught by the previous catch clause. */
}

Une autre possibilité est le gestionnaire fourre-tout, qui attrape tout objet jeté:

try {
    throw 10;
} catch (...) {
    std::cout << "caught an exception";
}

Renvoyer (propager) une exception

Parfois, vous voulez faire quelque chose avec l'exception que vous avez capturée (comme l'écriture pour vous connecter ou imprimer un avertissement) et la laisser remonter jusqu'au périmètre supérieur à traiter. Pour ce faire, vous pouvez relancer toute exception que vous avez détectée:

try {
    ... // some code here
} catch (const SomeException& e) {
    std::cout << "caught an exception";
    throw;
}

En utilisant throw; sans arguments rejettera l'exception actuellement interceptée.

C ++ 11

Pour relancer un fichier std::exception_ptr géré, la bibliothèque standard C ++ a la fonction rethrow_exception qui peut être utilisée en incluant l'en-tête <exception> dans votre programme.

#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
 
void handle_eptr(std::exception_ptr eptr) // passing by value is ok
{
    try {
        if (eptr) {
            std::rethrow_exception(eptr);
        }
    } catch(const std::exception& e) {
        std::cout << "Caught exception \"" << e.what() << "\"\n";
    }
}
 
int main()
{
    std::exception_ptr eptr;
    try {
        std::string().at(1); // this generates an std::out_of_range
    } catch(...) {
        eptr = std::current_exception(); // capture
    }
    handle_eptr(eptr);
} // destructor for std::out_of_range called here, when the eptr is destructed

Fonction Try Blocks In constructeur

La seule façon d'attraper une exception dans la liste d'initialisation:

struct A : public B
{
    A() try : B(), foo(1), bar(2)
    {
        // constructor body 
    }
    catch (...)
    {
        // exceptions from the initializer list and constructor are caught here
        // if no exception is thrown here
        // then the caught exception is re-thrown.
    }
 
private:
    Foo foo;
    Bar bar;
};

Fonction Essayez Bloquer pour une fonction régulière

void function_with_try_block() 
try
{
    // try block body
} 
catch (...) 
{ 
    // catch block body
}

Ce qui équivaut à

void function_with_try_block() 
{
    try
    {
        // try block body
    } 
    catch (...) 
    { 
        // catch block body
    }
}

Notez que pour les constructeurs et les destructeurs, le comportement est différent car le bloc catch renvoie une exception de toute façon (celui qui est attrapé s'il n'y a pas d'autre jet dans le corps du bloc catch).

La fonction main est autorisée à avoir un bloc try de fonction comme toute autre fonction, mais le bloc try de la fonction main n'acceptera pas les exceptions qui se produisent pendant la construction d'une variable statique non locale ou la destruction de toute variable statique. Au lieu de cela, std::terminate est appelé.

Fonction Try Blocks In destructor

struct A
{
    ~A() noexcept(false) try
    {
        // destructor body 
    }
    catch (...)
    {
        // exceptions of destructor body are caught here
        // if no exception is thrown here
        // then the caught exception is re-thrown.
    }
};

Notez que, bien que cela soit possible, il faut faire très attention au lancement du destructeur, comme si un destructeur appelé lors du déroulement de la pile lançait une exception, std::terminate est appelé.

Meilleure pratique: lancer par valeur, référence par const

En général, il est conseillé de lancer par valeur (plutôt que par pointeur), mais attraper par référence (const).

try {
    // throw new std::runtime_error("Error!");   // Don't do this!
    // This creates an exception object
    // on the heap and would require you to catch the
    // pointer and manage the memory yourself. This can
    // cause memory leaks!
    
    throw std::runtime_error("Error!");
} catch (const std::runtime_error& e) {
    std::cout << e.what() << std::endl;
}

Une des raisons pour lesquelles la capture par référence est une bonne pratique est qu’elle élimine le besoin de reconstruire l’objet lorsqu’elle est transmise au bloc catch (ou lorsqu’elle se propage à d’autres blocs catch). La saisie par référence permet également de gérer les exceptions de manière polymorphe et d'éviter le découpage d'objets. Cependant, si vous renvoyez une exception (comme throw e; voir exemple ci-dessous), vous pouvez toujours obtenir un découpage d'objet car le throw e; L'énoncé fait une copie de l'exception, quel que soit le type déclaré:

#include <iostream>

struct BaseException {
    virtual const char* what() const { return "BaseException"; }
};

struct DerivedException : BaseException {
    // "virtual" keyword is optional here
    virtual const char* what() const { return "DerivedException"; }
};

int main(int argc, char** argv) {
    try {
        try {
            throw DerivedException();
        } catch (const BaseException& e) {
            std::cout << "First catch block: " << e.what() << std::endl;
            // Output ==> First catch block: DerivedException

            throw e; // This changes the exception to BaseException
                     // instead of the original DerivedException!
        }
    } catch (const BaseException& e) {
        std::cout << "Second catch block: " << e.what() << std::endl;
        // Output ==> Second catch block: BaseException
    }
    return 0;
}

Si vous êtes certain que vous ne ferez rien pour modifier l'exception (comme ajouter des informations ou modifier le message), attraper par référence de référence permet au compilateur d'effectuer des optimisations et d'améliorer les performances. Mais cela peut toujours provoquer un épissage d'objet (comme dans l'exemple ci-dessus).

Avertissement: Faites attention aux exceptions non prévues dans catch blocs catch , en particulier en ce qui concerne l'allocation de mémoire ou de ressources supplémentaires. Par exemple, la construction de logic_error , runtime_error ou de leurs sous-classes peut bad_alloc raison de la mémoire bad_alloc lors de la copie de la chaîne d'exception, les flux d'E / S pouvant se produire lors de la journalisation

Exception imbriquée

C ++ 11

Lors de la gestion des exceptions, il existe un cas d'utilisation courant lorsque vous capturez une exception générique à partir d'une fonction de bas niveau (erreur de système de fichiers ou erreur de transfert de données, par exemple) et que ne pas être effectué (par exemple, ne pas pouvoir publier une photo sur le Web). Cela permet à la gestion des exceptions de réagir à des problèmes spécifiques avec des opérations de haut niveau et permet également, en ayant uniquement un message d'erreur, au programmeur de trouver une place dans l'application où une exception s'est produite. L'inconvénient de cette solution est que le bloc d'appel des exceptions est tronqué et que l'exception d'origine est perdue. Cela oblige les développeurs à inclure manuellement le texte de l'exception d'origine dans celui qui vient d'être créé.

Les exceptions imbriquées visent à résoudre le problème en attachant une exception de bas niveau, qui décrit la cause, à une exception de haut niveau, qui décrit ce que cela signifie dans ce cas particulier.

std::nested_exception permet d'imbriquer des exceptions grâce à std::throw_with_nested :

#include <stdexcept>
#include <exception>
#include <string>
#include <fstream>
#include <iostream>

struct MyException
{
    MyException(const std::string& message) : message(message) {}
    std::string message;
};

void print_current_exception(int level)
{
    try {
        throw;
    } catch (const std::exception& e) {
        std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
    } catch (const MyException& e) {
        std::cerr << std::string(level, ' ') << "MyException: " << e.message << '\n';
    } catch (...) {
        std::cerr << "Unkown exception\n";
    }
}

void print_current_exception_with_nested(int level =  0)
{
    try {
        throw;
    } catch (...) {
        print_current_exception(level);
    }    
    try {
        throw;
    } catch (const std::nested_exception& nested) {
        try {
            nested.rethrow_nested();
        } catch (...) {
            print_current_exception_with_nested(level + 1); // recursion
        }
    } catch (...) {
        //Empty // End recursion
    }
}

// sample function that catches an exception and wraps it in a nested exception
void open_file(const std::string& s)
{
    try {
        std::ifstream file(s);
        file.exceptions(std::ios_base::failbit);
    } catch(...) {
        std::throw_with_nested(MyException{"Couldn't open " + s});
    }
}
 
// sample function that catches an exception and wraps it in a nested exception
void run()
{
    try {
        open_file("nonexistent.file");
    } catch(...) {
        std::throw_with_nested( std::runtime_error("run() failed") );
    }
}
 
// runs the sample function above and prints the caught exception
int main()
{
    try {
        run();
    } catch(...) {
        print_current_exception_with_nested();
    }
}

Sortie possible:

exception: run() failed
 MyException: Couldn't open nonexistent.file
  exception: basic_ios::clear

Si vous travaillez uniquement avec des exceptions héritées de std::exception , le code peut même être simplifié.

std :: uncaught_exceptions

c ++ 17

C ++ 17 introduit int std::uncaught_exceptions() (pour remplacer la bool std::uncaught_exception() limitée) pour savoir combien d'exceptions sont actuellement non capturées. Cela permet à une classe de déterminer si elle est détruite lors du déroulement ou non d’une pile.

#include <exception>
#include <string>
#include <iostream>

// Apply change on destruction:
// Rollback in case of exception (failure)
// Else Commit (success)
class Transaction
{
public:
    Transaction(const std::string& s) : message(s) {}
    Transaction(const Transaction&) = delete;
    Transaction& operator =(const Transaction&) = delete;
    void Commit() { std::cout << message << ": Commit\n"; }
    void RollBack() noexcept(true) { std::cout << message << ": Rollback\n"; }

    // ...

    ~Transaction() {
        if (uncaughtExceptionCount == std::uncaught_exceptions()) {
            Commit(); // May throw.
        } else { // current stack unwinding
            RollBack();
        }
    }

private:
    std::string message;
    int uncaughtExceptionCount = std::uncaught_exceptions();
};

class Foo
{
public:
    ~Foo() {
        try {
            Transaction transaction("In ~Foo"); // Commit,
                                            // even if there is an uncaught exception
            //...
        } catch (const std::exception& e) {
            std::cerr << "exception/~Foo:" << e.what() << std::endl;
        }
    }
};

int main()
{
    try {
        Transaction transaction("In main"); // RollBack
        Foo foo; // ~Foo commit its transaction.
        //...
        throw std::runtime_error("Error");
    } catch (const std::exception& e) {
        std::cerr << "exception/main:" << e.what() << std::endl;
    }
}

Sortie:

In ~Foo: Commit
In main: Rollback
exception/main:Error

Exception personnalisée

Vous ne devriez pas lancer de valeurs brutes en tant qu'exceptions, utilisez plutôt l'une des classes d'exception standard ou créez la vôtre.

Avoir votre propre classe d'exception héritée de std::exception est un bon moyen de s'y prendre. Voici une classe d'exception personnalisée qui hérite directement de std::exception :

#include <exception>

class Except: virtual public std::exception {
    
protected:

    int error_number;               ///< Error number
    int error_offset;               ///< Error offset
    std::string error_message;      ///< Error message
    
public:

    /** Constructor (C++ STL string, int, int).
     *  @param msg The error message
     *  @param err_num Error number
     *  @param err_off Error offset
     */
    explicit 
    Except(const std::string& msg, int err_num, int err_off):
        error_number(err_num),
        error_offset(err_off),
        error_message(msg)
        {}

    /** Destructor.
     *  Virtual to allow for subclassing.
     */
    virtual ~Except() throw () {}

    /** Returns a pointer to the (constant) error description.
     *  @return A pointer to a const char*. The underlying memory
     *  is in possession of the Except object. Callers must
     *  not attempt to free the memory.
     */
    virtual const char* what() const throw () {
       return error_message.c_str();
    }
    
    /** Returns error number.
     *  @return #error_number
     */
    virtual int getErrorNumber() const throw() {
        return error_number;
    }
    
    /**Returns error offset.
     * @return #error_offset
     */
    virtual int getErrorOffset() const throw() {
        return error_offset;
    }

};

Un exemple jet catch:

try {
    throw(Except("Couldn't do what you were expecting", -12, -34));
} catch (const Except& e) {
    std::cout<<e.what()
             <<"\nError number: "<<e.getErrorNumber()
             <<"\nError offset: "<<e.getErrorOffset();
}

Comme vous ne faites pas que lancer un message d'erreur idiot, mais aussi d'autres valeurs représentant exactement l'erreur, votre gestion des erreurs devient beaucoup plus efficace et significative.

Il y a une classe d'exception qui vous permet de gérer std::runtime_error messages d'erreur: std::runtime_error

Vous pouvez également hériter de cette classe:

#include <stdexcept>

class Except: virtual public std::runtime_error {
    
protected:

    int error_number;               ///< Error number
    int error_offset;               ///< Error offset
    
public:

    /** Constructor (C++ STL string, int, int).
     *  @param msg The error message
     *  @param err_num Error number
     *  @param err_off Error offset
     */
    explicit 
    Except(const std::string& msg, int err_num, int err_off):
        std::runtime_error(msg)
        {
            error_number = err_num;
            error_offset = err_off;
            
        }

    /** Destructor.
     *  Virtual to allow for subclassing.
     */
    virtual ~Except() throw () {}
    
    /** Returns error number.
     *  @return #error_number
     */
    virtual int getErrorNumber() const throw() {
        return error_number;
    }
    
    /**Returns error offset.
     * @return #error_offset
     */
    virtual int getErrorOffset() const throw() {
        return error_offset;
    }

};

Notez que je n'ai pas remplacé la fonction what() de la classe de base ( std::runtime_error ), c'est-à-dire que nous utiliserons la version de what() la classe de base. Vous pouvez le remplacer si vous avez d'autres ordres du jour.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow