Si je génère moi-même une exception, je peux inclure n'importe quelle information dans l'exception: un certain nombre de lignes de code et le nom du fichier source. Quelque chose comme ça:
throw std::exception("myFile.cpp:255");
Mais qu’en est-il des exceptions non gérées ou des exceptions générées par moi?
Il semble que tout le monde essaie d'améliorer votre code pour créer des exceptions, et personne ne tente de répondre à la question que vous avez posée.
Ce qui est parce que cela ne peut pas être fait. Si le code qui lève l'exception est uniquement présenté sous forme binaire (par exemple, dans un fichier LIB ou DLL]), le numéro de ligne est parti et il n'y a aucun moyen de connecter l'objet à une ligne du fichier. code source.
Une meilleure solution consiste à utiliser une classe personnalisée et une macro. :-)
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
class my_exception : public std::runtime_error {
std::string msg;
public:
my_exception(const std::string &arg, const char *file, int line) :
std::runtime_error(arg) {
std::ostringstream o;
o << file << ":" << line << ": " << arg;
msg = o.str();
}
~my_exception() throw() {}
const char *what() const throw() {
return msg.c_str();
}
};
#define throw_line(arg) throw my_exception(arg, __FILE__, __LINE__);
void f() {
throw_line("Oh no!");
}
int main() {
try {
f();
}
catch (const std::runtime_error &ex) {
std::cout << ex.what() << std::endl;
}
}
Il existe plusieurs possibilités pour savoir où l'exception a été levée:
Utilisation des macros du compilateur
Utilisation de macros __FILE__
et __LINE__
à l'emplacement de projection (comme le montrent déjà d'autres commentateurs), soit en les utilisant dans des exceptions std sous forme de texte, soit en tant qu'arguments distincts d'une exception personnalisée:
Soit utiliser
throw std::runtime_error(msg " at " `__FILE__` ":" `__LINE__`);
ou jeter
class my_custom_exception {
my_custom_exception(const char* msg, const char* file, unsigned int line)
...
Notez que même lors de la compilation pour Unicode (dans Visual Studio),FICHIERse développe en une chaîne à un octet. Cela fonctionne dans le débogage et la libération. Malheureusement, les noms de fichier source avec des exceptions de code jeté sont placés dans l'exécutable de sortie.
Stack Walking
Découvrez l'emplacement de l'exception en parcourant la pile d'appels.
Sous Linux avec gcc, les fonctions backtrace () et backtrace_symbols () peuvent obtenir des informations sur la pile d'appels en cours. Voir la documentation gcc comment les utiliser. Le code doit être compilé avec -g pour que les symboles de débogage soient placés dans l'exécutable.
Sous Windows, vous pouvez parcourir la pile en utilisant la bibliothèque dbghelp et sa fonction StackWalk64. Voir article de Jochen Kalmbach sur CodeProject pour plus de détails. Cela fonctionne dans les versions debug et release, et vous devez livrer des fichiers .pdb pour tous les modules pour lesquels vous souhaitez des informations.
Vous pouvez même combiner les deux solutions en collectant des informations sur la pile d'appels lorsqu'une exception personnalisée est générée. La pile d'appels peut être stockée dans l'exception, comme dans .NET ou Java. Notez que la collecte des piles d'appels sur Win32 est très lente (mon dernier test indiquait environ 6 piles d'appels collectées par seconde). Si votre code génère de nombreuses exceptions, cette approche ralentit considérablement votre programme.
Si vous avez une génération de débogage et l'exécutez dans le débogueur Visual Studio, vous pouvez alors accéder au débogueur dès qu'un type d'exception est généré, avant qu'il ne se propage au monde.
Activez cette option avec l'option de menu Debug> Exceptions , puis cochez les types d'exceptions qui vous intéressent.
Vous pouvez également ajouter la possibilité de créer un fichier de vidage, si le code source de l'application est le vôtre. Avec le fichier de vidage et les fichiers PDB (symboles) pour la construction spécifique, vous obtiendrez des stacktraces avec WinDbg, par exemple.
La solution la plus simple consiste à utiliser une macro:
#define throw_line(msg) \
throw std::exception(msg " " __FILE__ ":" __LINE__)
void f() {
throw_line("Oh no!");
}
Je pense qu'une trace de pile devrait vous amener au but.
J'ai trouvé 2 solutions, mais aucune n'est totalement satisfaisante:
Si vous appelez std::set_terminate
, vous pouvez à partir de là imprimer la pile d’appel directement à partir de la levée d’exception tierce. Malheureusement, il n'y a aucun moyen de récupérer un gestionnaire de terminaison, et votre application va donc mourir.
Si vous appelez std::set_unexpected
, vous devez déclarer autant que possible de vos fonctions avec throw(MyControlledException)
, de sorte que, lorsqu'elles lancent en raison de fonctions appelées par des tiers, votre unexpected_handler
pourra vous donner une idée précise de l'endroit où votre application a jeté. .
Inspiré par la réponse de Frank Krueger et la documentation de std :: nested_exception , je me suis rendu compte que vous pouvez combiner la réponse de Frank, que j'utilise depuis un moment, avec std :: nested_exception pour créer une trace de pile d'erreur complète. informations de fichier et de ligne. Par exemple avec mon implémentation, en cours d'exécution
#include "Thrower.h"
#include <iostream>
// runs the sample function above and prints the caught exception
int main ( )
{
try {
// [Doing important stuff...]
try {
std::string s = "Hello, world!";
try {
int i = std::stoi ( s );
}
catch ( ... ) {
thrower ( "Failed to convert string \"" + s + "\" to an integer!" );
}
}
catch ( Error& e ) {
thrower ( "Failed to [Do important stuff]!" );
}
}
catch ( Error& e ) {
std::cout << Error::getErrorStack ( e );
}
std::cin.get ( );
}
les sorties
ERROR: Failed to [Do important stuff]!
@ Location:c:\path\main.cpp; line 33
ERROR: Failed to convert string "Hello, world!" to an integer!
@ Location:c:\path\main.cpp; line 28
ERROR: invalid stoi argument
Voici ma mise en œuvre:
#include <sstream>
#include <stdexcept>
#include <regex>
class Error : public std::runtime_error
{
public:
Error ( const std::string &arg, const char *file, int line ) : std::runtime_error( arg )
{
loc = std::string ( file ) + "; line " + std::to_string ( line );
std::ostringstream out;
out << arg << "\n@ Location:" << loc;
msg = out.str( );
bareMsg = arg;
}
~Error( ) throw() {}
const char * what( ) const throw()
{
return msg.c_str( );
}
std::string whatBare( ) const throw()
{
return bareMsg;
}
std::string whatLoc ( ) const throw( )
{
return loc;
}
static std::string getErrorStack ( const std::exception& e, unsigned int level = 0)
{
std::string msg = "ERROR: " + std::string(e.what ( ));
std::regex r ( "\n" );
msg = std::regex_replace ( msg, r, "\n"+std::string ( level, ' ' ) );
std::string stackMsg = std::string ( level, ' ' ) + msg + "\n";
try
{
std::rethrow_if_nested ( e );
}
catch ( const std::exception& e )
{
stackMsg += getErrorStack ( e, level + 1 );
}
return stackMsg;
}
private:
std::string msg;
std::string bareMsg;
std::string loc;
};
// (Important modification here)
// the following gives any throw call file and line information.
// throw_with_nested makes it possible to chain thrower calls and get a full error stack traceback
#define thrower(arg) std::throw_with_nested( Error(arg, __FILE__, __LINE__) )
`` `
En plus d'utiliser une classe personnalisée avec une macro, comme le suggère Frank Krueger, pour vos propres exceptions, vous voudrez peut-être jeter un coup d'œil au mécanisme de gestion des exceptions structuré (vous programmez sous Windows, n'est-ce pas?)
Check Gestion structurée des exceptions sur MSDN