2011-09-06 1 views
4

Il est recommandé que vous avez toujours quelque chose jettes dérivé de std::exception et il y a quelques spécialisations prédéfinit telles que std::runtime_errorstd exceptions invitant l'utilisation dangereuse?

l » interface std::exception est donnée en termes de accesseurs non lanceuse. Génial. Maintenant, regardez le constructeur pour std::runtime_error

class runtime_error : public exception { 
public: 
    explicit runtime_error (const string &); 
}; 

Donc, si je cette

try { 
    foo(); 
} 
catch (...) { 
    throw std :: runtime_error ("bang"); 
} 

il est tout à fait possible que foo jeté parce qu'il est hors de la mémoire, dans ce cas, la construction de l'argument string à runtime_error peut aussi jeter. Ce serait une expression qui jette aussi: cela n'appellera-t-il pas std::terminate?

Est-ce que cela signifie que nous ne devrions toujours le faire à la place:

namespace { 
    const std :: string BANG ("bang"); 
} 

... 

try { 
    foo(); 
} 
catch (...) { 
    throw std :: runtime_error (BANG); 
} 

MAIS ATTENDEZ cela ne fonctionnera pas non plus, il sera? Parce que runtime_error va copier son argument, qui peut également lancer ...

... cela ne veut pas dire qu'il n'y a pas de façon sûre d'utiliser les spécialisations standard de std::exception, et que vous devriez toujours rouler votre propre classe de chaînes dont le constructeur échoue seulement sans lancer?

Ou y at-il un truc qui me manque?

+0

Utilisez une implémentation de bibliothèque standard avec une optimisation de petite chaîne (sans allocation) et des messages d'erreur très courts? – UncleBens

Répondre

5

Je pense que votre problème principal est que vous faites catch(...) et la traduction à std::runtime_error perdant ainsi toutes les informations de type de l'exception originale. Vous devriez juste le relancer avec throw(). Pratiquement, si vous manquez de mémoire, vous avez probablement une exception bad_alloc levée à un moment donné et il n'y a pas grand-chose d'autre que vous pouvez ou devriez faire. Si vous voulez pour lancer une exception pour une raison autre qu'une allocation a échoué alors vous ne risquez pas d'avoir un problème à construire un objet d'exception sensible avec des informations contextuelles significatives. Si vous rencontrez un problème de mémoire lors de la mise en forme de votre objet d'exception, vous ne pouvez rien faire d'autre que propager l'erreur de mémoire.

Vous avez raison de dire qu'il existe un problème potentiel si vous construisez un objet chaîne pour construire une exception, mais si vous voulez formater un message avec un contexte, cela ne peut pas être évité en général. Notez que les objets d'exception standard ont tous un constructeur const char* (comme la semaine dernière), donc si vous avez un const char* que vous voulez utiliser, vous n'avez pas besoin de construire un nouvel objet std::string.

std::runtime_error doit copier son argument, mais pas nécessairement comme un nouvel objet chaîne. Il pourrait y avoir une zone de mémoire allouée statiquement à laquelle le contenu de son argument peut correspondre. Il suffit de remplir les conditions what(), ce qui ne nécessite que de retourner un const char *, il ne doit pas stocker un objet std::string.

+0

Eh bien - vous pouvez libérer de la mémoire dans certains cas et espérer qu'il n'est pas fragmenté. Il peut également être souligné que certains systèmes peuvent surcharger la mémoire, de sorte que l'exception sera levée lorsque le processus sera à court de mémoire virtuelle (dans le cas de systèmes 64 bits, cela ne signifie jamais "seulement" espace d'adressage de 48 bits). de 32 bits, il est généralement 3GiB en supposant 1GiB de haute mémoire). –

+1

@MaciejPiechotka: Dans le cas d'un système surchargeant, il est très peu probable qu'une exception C++ soit levée - du moins pas de manière conforme - car la mémoire 'error' ne sera détectée que là où elle n'est pas autorisée jeter une exception. –

+0

Ups. Désolé - il manque "seulement". IIRC chaque système lancera l'exception quand la mémoire virtuelle est épuisée et le sur-engagement lancera habituellement seulement alors (il pourrait tuer le processus cependant). Mon commentaire semble impliquer que l'exception sur l'épuisement de vm sera jetée seulement dans le système d'exploitation overcommiting qui, bien sûr, n'a aucun sens. –

3

Ceci serait une expression-throw qui elle-même lance aussi: est ce que n'appellera pas std :: terminate?

Non, ce ne serait pas le cas. Il serait juste jeter l'exception à propos de la mémoire insuffisante. Le contrôle n'atteindra pas la partie extérieure throw.

MAIS ATTENDEZ cela ne fonctionnera pas non plus, n'est-ce pas? Parce que runtime_error est va copier son argument, qui peut également jeter ...

cours d'exception avec une copie des constructeurs de lancer sont aussi mauvais que jeter Destructeurs. Rien qui puisse vraiment être fait à ce sujet.

3

std::runtime_error est conçu pour traiter les erreurs d'exécution habituelles, pas mémoire insuffisante ou d'autres exceptions critiques. La classe de base std::exception ne fait pas faire quelque chose qui peut lancer; pas plus std::bad_alloc. Et évidemment, remapper std::bad_alloc dans une exception qui nécessite une allocation dynamique pour fonctionner est une mauvaise idée.

1

La première chose est ce que vous voulez faire si vous avez une exception bad_alloc parce que votre mémoire est insuffisante?

Je dirais que dans un programme C++ classique, vous voudriez que le programme essaye d'une manière ou d'une autre de vous dire ce qui s'est passé et se termine ensuite.

Dans un programme C++ classique, vous laissez l'exception bad_alloc se propager dans la section principale du programme. La principale contiendra un arragement de try/catch comme ceci:

int main() 
{ 
    try 
    { 
     // your program starts 
    } 
    catch(const std::exception & e) 
    { 
     std::cerr << "huho something happened" << e.what() << std::endl; 
    } 
    catch(...) 
    { 
     std::cerr << "huho..err..what?" << std::endl; 
    } 
} 

vous ne pouvez utiliser catch (...) à l'intérieur du principal et aux fonctions de départ de fils. Contrairement à d'autres langages comme Java, vous n'êtes pas censé attraper toutes les exceptions possibles localement. Vous les laissez simplement se propager jusqu'à ce que vous les attrapiez là où vous le vouliez.

Maintenant, si vous avez du code qui doit vérifier spécifiquement std :: bad_alloc, vous ne devez catch (const std :: bad_alloc &) localement. Et là, il serait peut-être sage de faire autre chose plutôt que de simplement réitérer une autre exception. J'ai également trouvé dans le langage de programmation C++ §14.10 que le mécanisme de gestion des exceptions C++ garde un peu de mémoire sur lui-même pour contenir des exceptions, de sorte que lancer une exception de bibliothèque standard ne lèvera pas une exception par lui-même. Bien sûr, il est également possible de laisser le mécanisme de gestion des exceptions manquer de mémoire si vous codez réellement quelque chose de perverti. Donc, pour résumer, si vous ne faites rien et laissez de grandes exceptions comme bad_alloc propager bien où vous voulez les attraper, à mon avis, vous êtes en sécurité. Et vous ne devriez pas utiliser catch (...) ou catch (const std :: exception &) n'importe où sauf dans la fonction principale et dans les fonctions de démarrage des threads.

Catching toutes les exceptions pour réécrire une seule exception est vraiment la dernière chose à faire. Vous perdez tous les avantages que vous avez avec le mécanisme de gestion des exceptions C++.

Questions connexes