Guide de développement Boost-4.9exception

exception

Les exceptions sont un mécanisme important pour la gestion des erreurs C++. Elles modifient le mode de traitement traditionnel consistant à utiliser les valeurs de retour d'erreur, simplifient l'interface de fonction et le code d'appel, et aident à écrire des programmes propres, élégants et robustes. La norme C++ définit la classe d'exception standard std::exception et une série de sous-classes, qui constituent la base de la gestion des erreurs dans l'ensemble du langage C++.
La bibliothèque boost.exception a été renforcée pour remédier aux défauts des classes d'exception dans la bibliothèque standard. Elle permet une surcharge de l'opérateur <<, qui peut transmettre n'importe quelle donnée dans l'exception, ce qui contribue à augmenter l'information et l'expressivité de l'exception. Certaines de ses fonctionnalités ont été incluses dans le standard C+ +11.

L'exception se trouve dans l'espace de noms boost. Pour utiliser l'exception, vous devez inclure le fichier d'en-tête <boost/exception/all.hpp>, c'est-à-dire :

#include<boost/exception/all.hpp>
using namespace boost;

Exceptions dans la bibliothèque standard

Pour utiliser boost.exception, nous devons d'abord comprendre le système d'exception spécifié par la norme C++.

Le standard C++ définit une classe de base d'exceptions std::exception et un mécanisme de gestion des exceptions try/catch/throw. std::exception dérive plusieurs sous-classes pour décrire différents types d'exceptions, telles que bad_alloc, bad_cast, out_of_range, etc. Ensemble, nous avons construit le Cadre de gestion des exceptions C++.

C++ permet à n'importe quel type d'être lancé comme exception, mais après l'apparition de std::exception, nous devrions essayer de l'utiliser car std::exception fournit une fonction membre très utile what(), qui peut renvoyer les informations portées par l'exception. C'est mieux et plus sûr que de simplement lancer une valeur d'erreur entière ou une chaîne.

Si std::exception et ses sous-classes ne peuvent pas répondre aux exigences du programme en matière de gestion des exceptions, nous pouvons également en hériter et y ajouter davantage d'informations de diagnostic d'exception. Par exemple, si vous souhaitez ajouter des informations de code d'erreur à une exception, vous pouvez procéder comme suit :

class my_exception : public std::logic_error  //继承标准库异常类
{
    
    
private:
   int err_no;  //错误码信息
public:
   my_exception(const char* msg, int err) :  //构造函数
    std::logic_error(msg), err_no(err) {
    
    }  //初始化父类和错误码信息
   int get_err_no() //获取自定义的错误码信息
   {
    
     return err_no; }
};

Mais lorsque de nombreux types d'exceptions sont nécessaires dans le système, cette méthode d'implémentation devient rapidement une lourde charge pour le programmeur : une grande quantité de code très similaire doit être écrite pour s'adapter aux différentes informations.

Et il y a toujours un problème avec cette solution : souvent, lorsqu'une exception se produit, les informations de diagnostic complètes sur l'exception ne peuvent pas être obtenues, et une fois la classe d'exception de la bibliothèque standard lancée, elle devient un objet « mort », et le programme en perd le contrôle.capacité de contrôle, vous ne pouvez que l'utiliser ou lancer une nouvelle exception.

boost.exception définit une nouvelle classe d'exception boost::exception pour résoudre ces défauts, améliorant ainsi le mécanisme de gestion des exceptions standard C++.

Lorsque nous parlons d'exceptions dans la discussion suivante, sauf indication contraire, elles font toutes référence à boost::exception. Les exceptions dans la bibliothèque standard utilisent la forme std::exception.

Résumé du cours

La bibliothèque d'exceptions fournit deux classes : exception et error_info, qui constituent la base de la bibliothèque d'exceptions.

class exception
{
    
    
protected:
   exception();
   exception(exception const & x);
   virtual ~exception();
private :
   template <class E, class Tag, class T>
   friend E const & operator<<(E const &, error_info<Tag,T> const &);
};
typename value_type* get_error_info(E & x);

La classe d'exception a peu de fonctions membres publiques (mais un grand nombre de fonctions et de variables privées pour une implémentation interne), et le constructeur protégé montre son intention de conception : il s'agit d'une classe abstraite à laquelle personne ne peut accéder, à l'exception de ses sous-classes. créée ou détruite, ce qui garantit que l’exception ne peut pas être utilisée à mauvais escient.

La capacité importante d'exception réside dans son opérateur ami <<, qui peut stocker des informations sur l'objet error_info. Les informations stockées peuvent être get_error_info<>()récupérées à tout moment à l'aide d'une fonction gratuite. Cette fonction renvoie un pointeur pour stocker les données, ou un pointeur nul s'il n'y a pas ce type d'informations dans l'exception.

l'exception n'hérite pas intentionnellement de std::exception, car en réalité une grande quantité de code existant possède déjà de nombreuses sous-classes de std::exception, et si l'exception est également une sous-classe de std::exception, alors l'utilisation de l'héritage pour l'exception peut provoquer Problème d'héritage multiple "Diamant".

template <class Tag, class T>
class error_info
{
    
    
public:
   typedef T value_type;
   error_info(value_type const &v);
   value_type & value();
};

error_info fournit une solution générale pour ajouter des informations aux types d'exceptions. Le premier paramètre de type de modèle Tag est une balise. Il s'agit généralement (de préférence) d'une classe vide, utilisée uniquement pour marquer la classe error_info afin qu'elle génère un type différent lorsque le modèle est instancié. Le deuxième paramètre de type de modèle T correspond aux données d'informations réellement stockées, accessibles à l'aide de la fonction membre value().

Transmettre les informations à l'exception

exception et error_info sont conçus pour fonctionner avec std::exception, et les classes d'exceptions personnalisées peuvent hériter plusieurs fois en toute sécurité d'exception et std::exception pour bénéficier des capacités des deux.

Parce que l'exception est définie comme une classe abstraite, notre programme doit définir sa sous-classe pour l'utiliser. Comme mentionné ci-dessus, l'exception doit utiliser l'héritage virtuel. Habituellement, l'implémentation de la classe d'exception personnalisée est terminée une fois l'héritage terminé. Il n'est pas nécessaire d'y ajouter des variables membres ou des fonctions membres "superflues". Ces tâches ont déjà été accomplies par exception. Par exemple:

struct my_exception : //自定义异常类
virtual std::exception, //虚继承,struct默认public继承
virtual boost::exception //虚继承,struct默认public继承
{
    
    }; //空实现,不需要实现代码

Ensuite, nous devons définir les informations qui doivent être stockées pour l'exception - en utilisant la classe modèle error_info. Utilisez une structure comme premier paramètre de modèle pour marquer le type d'informations, puis utilisez le deuxième paramètre de modèle pour spécifier le type de données des informations. La définition du type étant error_info<>longue, pour des raisons de commodité d'utilisation, il est généralement nécessaire d'utiliser typedef.

Le code suivant utilise error_info pour définir deux classes d'informations qui stockent int et string :

int main()
try  //function-try块
{
    
    
	try
	{
    
    

		throw my_exception() << err_no(10); //抛出异常,存储错误码
	}
	catch (my_exception& e) //捕获异常,使用引用形式
	{
    
    

		cout << *get_error_info<err_no>(e) << endl;
		cout << e.what() << endl;
		e << err_str("other info"); //向异常追加信息
		throw; //再次抛出异常
	}
}
catch (my_exception& e) //function-try的捕获代码
{
    
    
	cout << *get_error_info<err_str>(e) << endl; //获得异常信息
}

Les commentaires dans le code ont expliqué beaucoup de choses, revenons-y.

Le programme définit d'abord une classe d'exception my_exception, puis utilise typedef pour définir deux types d'informations d'exception : err_no et err_str, et utilise int et string pour stocker respectivement le code d'erreur et les informations d'erreur. La fonction main() utilise le bloc function-try pour intercepter les exceptions. Elle inclut l'intégralité du corps de la fonction dans le bloc try, ce qui peut mieux séparer le code de gestion des exceptions du code de processus normal.

L'instruction throw my_exception() crée un objet temporaire de la classe d'exception my_exception et utilise immédiatement << pour lui transmettre l'objet err_no, en stockant le code d'erreur 10. Par la suite, l'exception est interceptée par le bloc catch et la fonction free get_error_info<err_no>(e)peut obtenir le pointeur de la valeur d'information stockée à l'intérieur de l'exception, il faut donc y accéder à l'aide de l'opérateur de déréférencement *.

Les exceptions peuvent également être complétées par des informations, également en utilisant l'opérateur <<. Enfin, dans la partie bloc catch de function-try, l'exception est finalement gérée et le programme se termine.

Classe de message d'erreur

À travers des exemples, nous avons une compréhension de base de l'utilisation de l'exception.Nous pouvons voir que l'utilisation de l'exception est assez claire et simple, et en même temps elle fournit des fonctions flexibles et puissantes, rendant la classe d'exception plus utile.

Étant donné que la définition de classe d'exception dérivée d'exception est très simple et n'a pas de code d'implémentation, vous pouvez facilement établir un système de classe d'exception sophistiqué et complet adapté à votre propre programme, de sorte que chaque classe d'exception ne corresponde qu'à un seul type d'erreur. Tant que l'héritage virtuel est utilisé, le système de classes peut être arbitrairement complexe pour exprimer pleinement la signification de l'erreur.

Une autre tâche importante dans la gestion des exceptions est de définir le type d'informations d'erreur.La méthode de base consiste à utiliser typedef pour concrétiser la classe de modèle error_info. Cela s'avère souvent fastidieux, surtout lorsqu'il existe un grand nombre de types de messages. Par conséquent, la bibliothèque d'exceptions fournit spécialement un certain nombre de classes de messages d'erreur prédéfinies, telles que logic_err et d'autres types définis par la bibliothèque standard, pour faciliter l'utilisation des programmeurs :

typedef error_info<...> errinfo_api_function;
typedef error_info<...> errinfo_at_iine;
typedef error_info<...> errinfo_errno;
typedef error_info<...> errinfo_file_handle;
typedef error_info<...> errinfo_file_name;
typedef error_info<...> errinfo_file_open_mode;
typedef error_info<...> errinfo_type_info_name;

Ils peuvent être utilisés pour traiter les informations d'erreur telles que les appels d'API courants, les numéros de ligne, les codes d'erreur, les descripteurs de fichiers, les noms de fichiers, etc. Par exemple:

try
{
    
    
     throw my_exception() << errinfo_api_function("call api")
           << errinfo_errno(101);
}
catch (boost::exception& e)
{
    
    
     cout << *get_error_info<errinfo_api_function>(e);
     cout << *get_error_info<errinfo_errno>(e);
}    

De plus, la bibliothèque d'exceptions fournit également trois types de messages d'erreur prédéfinis, mais les règles de dénomination sont légèrement différentes :

typedef error_info<...>  throw_function;
typedef error_info<...>  throw_file;
typedef error_info<...>  throw_line;

Ces trois types de messages d'erreur sont principalement utilisés pour stocker les informations du code source. Utilisés avec les macros BOOST_CURRENT_FUNCTION, _FILE_ et _LINE_, vous pouvez obtenir le nom de la fonction appelante, le nom du fichier source et le numéro de ligne du code source.

Mais si ces classes prédéfinies ne peuvent pas répondre aux exigences, nous devons à nouveau utiliser typedef. Afin de résoudre ce problème mineur, nous pouvons personnaliser une macro d'outil auxiliaire DEFINE_ERROR_INPO, qui peut implémenter rapidement et facilement la définition de error_info :

#define DEFINE_ERROR_INFO(type, name) \
   typedef boost::error_info<struct tag_##name, type> name

La macro DEFINE_ERROR_INFO accepte deux paramètres, type est le type qu'elle souhaite stocker et name est le nom du type de message d'erreur requis. Utilisez la commande de prétraitement ## pour créer la classe d'étiquette requise par error_info. Son utilisation est aussi simple que de déclarer une variable :

DEFINE_ERROR_INFO(int, err_no);

Après développement de la macro, cela équivaut à :

typedef boost::error_info<struct tag_err_no, int> err_no;

Normes d'emballage anormales

La bibliothèque d'exceptions fournit une fonction modèle activate_error_info(T &e), où T est une classe d'exception standard ou un autre type personnalisé. Il peut envelopper le type T pour générer une classe dérivée de boost::exception et T, obtenant ainsi tous les avantages de boost::exception sans modifier le système de gestion des exceptions d'origine. Si le type T est déjà une sous-classe de boost::exception, activate_error_info renverra une copie de e.

Enable_error_info() est généralement utilisé lorsque des classes d'exception existent déjà dans le programme et qu'il est difficile, voire impossible, de modifier ces classes d'exception (par exemple, si elles ont été compilées dans une bibliothèque). À l'heure actuelle, activate_error_info() peut envelopper la classe d'exception d'origine, ce qui facilite l'intégration de boost::exception dans le système d'exception d'origine sans modifier le code existant.

Le code démontrant l'utilisation de activate_error_info() est le suivant :

struct my_err {
    
    }; //某个自定义的异常库,未使用boost::exception

int main()
{
    
    
	try
	{
    
    
	    //使用enable_error_info包装自定义异常
		throw enable_error_info(my_err()) << errinfo_errno(10);
	}
	catch (boost::exception& e) //这里必须使用boost::exception来捕获
	{
    
    
		cout << *get_error_info<errinfo_errno>(e) << endl;
	}
}

Faites attention à l'utilisation de catch dans le code. L'objet renvoyé par activate_error_info() est une sous-classe de boost::exception et my_err. Le paramètre de catch peut être l'un des deux, mais si vous souhaitez utiliser les informations stockées par boost::exception, vous devez utiliser boost::exception pour intercepter les exceptions.

Enable_error_info() peut également encapsuler les exceptions de bibliothèque standard :

throw enable_error_info(std::runtime_error("runtime"))
                  << errinfo_at_line(_LINE_); //包装标准异常

Utiliser une fonction pour lever une exception

La bibliothèque d'exceptions ajoute de nombreuses nouvelles fonctions aux exceptions et apporte de nombreux avantages. Cependant, pour diverses raisons, les classes d'exceptions du programme ne peuvent pas toujours hériter de boost::exception et doivent être empaquetées avec activate_error_info().

La bibliothèque d'exceptions fournit la fonction throw_exception() dans le fichier d'en-tête <boost/throw_exception.hpp> pour simplifier l'appel de activate_error_info(). Elle peut remplacer l'instruction throw d'origine pour lever une exception et utiliser automatiquement activate_error_info() pour envelopper l'exception. objet et prend en charge la sécurité des threads, ce qui est mieux que d'utiliser throw directement, équivalent à :

throw (boost::enable_error_info(e))

Cela garantit que l'exception levée est une sous-classe de boost::exception et que les informations sur l'exception peuvent être ajoutées. Par exemple:

throw_exception (std::runtime_error("runtime"));

Sur la base de throw_exception(), la bibliothèque d'exceptions fournit une macro très utile BOOST_THROW_EXCEPTION, qui appelle boost::throw_exception() et activate_error_info(), afin qu'elle puisse accepter n'importe quel type d'exception et utiliser throw_function, throw_file et throw_line pour ajouter automatiquement des informations telles que comme nom de fonction, nom de fichier et numéro de ligne où l'exception s'est produite.

Cependant, il convient de noter que afin d'assurer la compatibilité avec la macro de configuration BOOST_NO_EXCEPTIONS, la fonction throw_exception() et la macro BOOST_THROW_EXCEPTION nécessitent que le paramètre e soit une sous-classe de std::exception.

Ce n'est généralement pas un problème pour les classes d'exceptions nouvellement écrites, car elles héritent toujours de std::exception et boost::exception, mais s'il y a des exceptions dérivées non-std::exception dans l'ancien code, elles ne peuvent pas être utilisées, ce qui est certain. Cela limite dans une certaine mesure la portée d'application de throw_exception() et BOOST_NO_EXCEPTIONS.

Si vous vous assurez de toujours utiliser boost::exception dans votre programme et que vous ne définissez pas la macro de configuration BO0ST_NO_EXCEPTIONS (cela devrait être la majorité des cas), vous pouvez modifier le code source de Boost et commenter throw_exception_assert_compatibility(e) dans <boost/ throw_exception.hpp> Cette instruction pour supprimer cette restriction.

Obtenir plus d'informations sur le débogage

L'exception offre la possibilité de stocker facilement des informations. Vous pouvez y ajouter n'importe quelle quantité d'informations, mais lorsque l'objet d'exception est ajouté plusieurs fois avec des données à l'aide de l'opérateur<<, cela l'amènera à stocker une grande quantité d'informations (BOOST_THROW_EXCEPTION est un bon exemple) . Si l'on utilise toujours la fonction gratuite get_error_info pour récupérer élément par élément, cela peut être gênant voire impossible. Dans ce cas, nous avons besoin d'une autre fonction : diagnostic_information().

diagnostic_information() peut afficher toutes les informations contenues dans l'exception. Si l'exception est levée par la macro BOOST_THROW_EXCEPTION, elle peut être assez volumineuse et peu conviviale, mais elle peut fournir de bonnes informations sur les erreurs de diagnostic aux développeurs de programmes.

Le code démontrant l'utilisation de BOOST_THROW_EXCEPTION et diagnostic_information() est le suivant :

struct my_err() //自定义的异常类

int main()
{
    
    
	try
	{
    
    
	    //使用enable_error_info包装自定义异常
		throw enable_error_info(my_err())
			<< errinfo_errno(101)
			<< errinfo_api_function("fopen");
	}
	catch (boost::exception& e)
	{
    
    
		cout << diagnostic_information(e) << endl;
	}

	try
	{
    
    
		BOOST_THROW_EXCEPTION(std::logic_error("logic")); //必须是标准异常
	}
	catch (boost::exception& e)
	{
    
    
		cout << diagnostic_information(e) << endl;
	}
}

Les résultats en cours d'exécution montrent la différence entre les instructions throw ordinaires et BOOST_THROW_EXCEPTION, qui peuvent afficher plus d'informations utiles pour le débogage.

Il existe également une fonction plus pratique current_exception_diagnostic_information() dans la bibliothèque d'exceptions. Elle ne peut être utilisée qu'à l'intérieur du bloc catch et renvoie la chaîne de diagnostic d'exception sous la forme std::string. Cela élimine les petits problèmes liés à la spécification des paramètres d'exception.

Conditionnement des informations sur les exceptions

Exception prend en charge l'utilisation de boost::tuple pour combiner et regrouper les informations d'exception. Lorsqu'il existe de nombreux types d'informations d'exception et qu'elles apparaissent souvent en groupes, cela peut simplifier l'écriture des exceptions levées. Par exemple:

//tuple类型定义
typedef tuple<errinfo_api_function, errinfo_errno> err_group;
try
{
    
    
    //使用enable_error_info包装自定义异常
    throw enable_error_info(std::out_of_range("out"))
        << err_group("syslogd", 874);
}
catch (boost::exception&)
{
    
    
    cout << current_exception_diagnostic_information() << endl;
}

conversion de types

La fonction modèle current_exception_cast<E>()fournit une opération de conversion similaire à la bibliothèque standard. Elle est similaire à current_exception_diagnostic_information() et ne peut être utilisée qu'à l'intérieur du bloc catch. Elle peut convertir l'objet d'exception en un pointeur vers le type E. Si l'objet d'exception ne peut pas être converti dans E*, un pointeur nul est renvoyé. .

Par exemple, le code suivant transforme boost::exception en std::exception, à condition que l'exception soit une sous-classe de std::exception :

catch (boost::exception&)
{
    
    
    cout << current_exception_cast<std::exception>()->what();
}

Exceptions transmises entre les threads

La bibliothèque d'exceptions prend en charge la transmission d'exceptions entre les threads, ce qui nécessite l'utilisation de la capacité de clonage de boost::exception. L'utilisation de activate_current_exception() pour envelopper l'objet d'exception ou l'utilisation de throw_exception() peut encapsuler l'objet d'exception afin qu'il puisse être cloné.

Lorsqu'une exception se produit, le thread doit appeler la fonction current_exception() dans le bloc catch pour obtenir l'objet pointeur exception_ptr de l'objet d'exception actuel. Il pointe vers une copie de l'objet d'exception. Il est thread-safe et peut appartenir et modifié simultanément par plusieurs threads en même temps. rethrow_exception() peut relancer les exceptions.

Le code qui illustre la gestion des exceptions dans un thread est le suivant :

void thread_work() //线程工作函数
{
    
    
     throw_exception(std::exception("test"));
}
int main()
{
    
    
     try
     {
    
    
         thread_work();
     }
     catch(...)
     {
    
    
          exception_ptr e = current_exception();
          cout << current_exception_diagnostic_information();
     }
}

Cette utilisation de boost::exception a été incluse dans la norme C++11

exemple de code

#include <iostream>
using namespace std;

#include <boost/exception/all.hpp>
using namespace boost;
//

class my_exception_test : public std::logic_error
{
    
    
private:
	int err_no;
public:
	my_exception_test(const char* msg, int err) :
		std::logic_error(msg), err_no(err) {
    
    }
	int get_err_no()
	{
    
    
		return err_no;
	}
};

struct my_exception :
	virtual std::exception,
	virtual boost::exception
{
    
    };

typedef boost::error_info<struct tag_err_no, int>     err_no;
typedef boost::error_info<struct tag_err_str, string> err_str;

//
void case1()
try
{
    
    
	try
	{
    
    

		throw my_exception() << err_no(10);
	}
	catch (my_exception& e)
	{
    
    

		cout << *get_error_info<err_no>(e) << endl;
		cout << e.what() << endl;
		e << err_str("other info");
		throw;
	}
}
catch (my_exception& e)
{
    
    
	cout << *get_error_info<err_str>(e) << endl;
}

//
#define DEFINE_ERROR_INFO(type, name) \
    typedef boost::error_info<struct tag_##name, type> name

void case2()
try
{
    
    
	throw my_exception() << errinfo_api_function("call api")
		<< errinfo_errno(101);
}
catch (boost::exception& e)
{
    
    
	cout << *get_error_info<errinfo_api_function>(e);
	cout << *get_error_info<errinfo_errno>(e);
	cout << endl;
}

//
struct my_err {
    
    };

void case3()
{
    
    
	try
	{
    
    
		throw enable_error_info(my_err()) << errinfo_errno(10);
		throw enable_error_info(std::runtime_error("runtime"))
			<< errinfo_at_line(__LINE__);

	}
	catch (boost::exception& e)
	{
    
    
		cout << *get_error_info<errinfo_errno>(e) << endl;
	}
}

//
#include <boost/throw_exception.hpp>

void case4()
{
    
    
	try
	{
    
    
		throw enable_error_info(my_err())
			<< errinfo_errno(101)
			<< errinfo_api_function("fopen");
	}
	catch (boost::exception& e)
	{
    
    
		cout << diagnostic_information(e) << endl;
	}

	try
	{
    
    
		BOOST_THROW_EXCEPTION(std::logic_error("logic"));
	}
	catch (boost::exception& e)
	{
    
    
		cout << diagnostic_information(e) << endl;
	}
}

int main()
{
    
    
	case1();
	case2();
	case3();
	case4();
}

Je suppose que tu aimes

Origine blog.csdn.net/qq_36314864/article/details/132491947
conseillé
Classement