Question

    

Cette question a déjà une réponse ici:

         

Je viens de terminer le travail sur un C ++ - programme où j'ai mis mes propres exceptions (bien que dérivé de std :: exception). La pratique j'ai appliqué quand une exception provoque une réaction en chaîne, la propagation de l'erreur vers le haut et qui donne lieu à d'autres exceptions, est de concaténer le message d'erreur à chaque pas approprié pour les modules (lecture des classes). C'est à dire. l'ancienne exception elle-même est abandonnée et une nouvelle exception est créée, mais avec une plus longue message d'erreur.

Cela peut avoir travaillé pour mon petit programme, mais je n'étais pas satisfait de mon approche à la fin. D'une part, les numéros de ligne (mais pas appliquées au moment) et les noms de fichiers ne sont pas conservés à l'exception de la dernière exception; et vraiment que l'information est de plus grand intérêt dans la première exception.

Je figure cela aurait pu être mieux gérée en enchaînant exceptions ensemble; à savoir l'ancienne exception est prévue dans le constructeur de la nouvelle exception. Mais comment serait-ce mis en œuvre? Ne pas les exceptions meurent quand ils sortent du champ d'application de la méthode, empêchant ainsi l'un à l'utilisation des pointeurs d'exception? Et comment copier et stocker l'exception si l'exception peut être de toute classe dérivée?

Cela me conduit finalement à se demander si enchaînant des exceptions en C ++ est une bonne idée après tout. Peut-être que l'on devrait simplement créer une exception près, puis ajouter des données supplémentaires que (comme je l'ai fait, mais probablement d'une manière beaucoup mieux)?

Quelle est votre réponse à cela? Les exceptions devraient causées par un autre être enchaînées pour conserver une sorte de « trace d'exception » - et comment devrait-il être mis en œuvre? - ou si une seule exception et utiliser des données supplémentaires qui s'y rattachent - et comment devrait-on faire

Était-ce utile?

La solution

Il est nécessaire de copier les données sur un objet d'exception, dans une chaîne, si vous voulez qu'il survive le bloc catch qui le reçoit, en dehors de rethrow par throw;. (Ce qui comprend, par exemple, si les sorties de bloc catch par un throw obj;.)

Cela peut être fait en mettant les données à enregistrer sur le tas, et la mise en œuvre swap (move en C ++ 0x) sur vos données privées à l'intérieur à l'exception, par exemple.

Bien sûr, vous devez être prudent lorsque vous utilisez le tas avec des exceptions ... mais là encore, dans la plupart des systèmes d'exploitation modernes, overcommitment mémoire empêche complètement new de jamais jeter, pour le meilleur ou pour le pire. Une marge mémoire bonne et déposer des exceptions de la chaîne sur la fusion complète devrait garder en sécurité.

struct exception_data { // abstract base class; may contain anything
    virtual ~exception_data() {}
};

struct chained_exception : std::exception {
    chained_exception( std::string const &s, exception_data *d = NULL )
        : data(d), descr(s) {
        try {
            link = new chained_exception;
            throw;
        } catch ( chained_exception &prev ) {
            swap( *link, prev );
        } // catch std::bad_alloc somehow...
    }

    friend void swap( chained_exception &lhs, chained_exception &rhs ) {
        std::swap( lhs.link, rhs.link );
        std::swap( lhs.data, rhs.data );
        swap( lhs.descr, rhs.descr );
    }

    virtual char const *what() const throw() { return descr.c_str(); }

    virtual ~chained_exception() throw() {
        if ( link && link->link ) delete link; // do not delete terminator
        delete data;
    }

    chained_exception *link; // always on heap
    exception_data *data; // always on heap
    std::string descr; // keeps data on heap

private:
    chained_exception() : link(), data() {}
    friend int main();
};

void f() {
    try {
        throw chained_exception( "humbug!" );
    } catch ( std::exception & ) {
        try {
            throw chained_exception( "bah" );
        } catch ( chained_exception &e ) {
            chained_exception *ep = &e;
            for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
                std::cerr << ep->what() << std::endl;
            }
        }
    }

    try {
        throw chained_exception( "meh!" );
    } catch ( chained_exception &e ) {
        for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
            std::cerr << ep->what() << std::endl;
        }
    }
}

int main() try {
    throw chained_exception(); // create dummy end-of-chain
} catch( chained_exception & ) {
    // body of main goes here
    f();
}

sortie (de manière appropriée grincheux):

bah
humbug!
meh!

Autres conseils

Étant donné que cette question a été posée des changements notables ont été apportés à la norme C ++ 11. Je manque sans cesse ce dans les discussions sur les exceptions, mais l'approche suivante, des exceptions de nidification, le tour est joué:

std::nested_exception et std::throw_with_nested

Il est décrit sur StackOverflow et ici , comment vous pouvez obtenir une trace sur vos exceptions dans votre code sans avoir besoin d'un débogueur ou encombrant exploitation forestière, en écrivant simplement un gestionnaire d'exception appropriée qui réémettre exceptions imbriquées .

Comme vous pouvez le faire avec une classe d'exception dérivée, vous pouvez ajouter beaucoup d'informations à un tel backtrace! Vous pouvez également jeter un oeil à mon MWE sur GitHub, où un backtrace 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"

Vous pouvez vouloir regarder ceci: http://www.boost.org/doc/libs/1_43_0/libs/exception/doc/boost-exception.html

Il est approche quelque peu différente de ce que MS a fait en C #, mais il semble correspondre à vos besoins.

Une autre idée est d'ajouter les données pertinentes à votre objet d'exception puis utilisez une déclaration de throw; nue pour re-jeter. Je pense que les informations de la pile est retenue dans ce cas, et ainsi vous saurez toujours la source d'origine de l'exception, mais les tests serait une bonne idée.

Je parie depuis si oui ou non des informations pile est disponible à tout est mise en œuvre définie, que les implémentations varient encore plus largement que ce soit dans ou non conservé en aucune manière après une déclaration de throw; nue.

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