2010-12-05 29 views
20

J'ai ce code ..Est-il acceptable de lancer manuellement un fichier std :: bad_alloc?

CEngineLayer::CEngineLayer(void) 
{ 
    // Incoming creation of layers. Wrapping all of this in a try/catch block is 
    // not helpful if logging of errors will happen. 

    logger = new (std::nothrow) CLogger(this); 

    if(logger == 0) 
    { 
    std::bad_alloc exception; 
    throw exception; 
    } 

    videoLayer = new (std::nothrow) CVideoLayer(this); 

    if(videoLayer == 0) 
    { 
    logger->log("Unable to create the video layer!"); 

    std::bad_alloc exception; 
    throw exception; 
    } 
} 

IEngineLayer* createEngineLayer(void) 
{ 
    // Using std::nothrow would be a bad idea here as catching things thrown 
    // from the constructor is needed. 

    try 
    { 
    CEngineLayer* newLayer = new CEngineLayer; 

    return (IEngineLayer*)newLayer; 
    } 
    catch(std::bad_alloc& exception) 
    { 
    // Couldn't allocate enough memory for the engine layer. 
    return 0; 
    } 
} 

J'ai omis la plupart des informations non liées, mais je pense que l'image est clair.

Est-il acceptable de lancer manuellement un fichier std :: bad_alloc au lieu d'essayer/capturer toutes les créations de calques individuellement et de les consigner avant de relancer bad_allocs?

+0

Une petite note, si vous n'utilisez pas de pointeur intelligent pour logger, cela risque de fuir si le constructeur de CVideoLayer se lance. –

+0

J'ai édité la couche vidéo car je n'ai pas encore de couche vidéo et je voulais montrer mon problème. J'ai décidé de le rendre simple plutôt que précis. – Jookia

Répondre

18

Vous n'avez pas besoin de faire cela. Vous pouvez utiliser le formulaire parameterless de la déclaration throw pour attraper l'exception std::bad_alloc, connectez-il, réémettre il:

logger = new CLogger(this); 
try { 
    videoLayer = new CVideoLayer(this); 
} catch (std::bad_alloc&) { 
    logger->log("Not enough memory to create the video layer."); 
    throw; 
} 

Ou, si logger n'est pas un pointeur intelligent (il devrait être):

logger = new CLogger(this); 
try { 
    videoLayer = new CVideoLayer(this); 
} catch (std::bad_alloc&) { 
    logger->log("Not enough memory to create the video layer."); 
    delete logger; 
    throw; 
} catch (...) { 
    delete logger; 
    throw; 
} 
+0

+1; faites comme ça - faites des exceptions pour vous; ne fonctionne pas pour les exceptions. –

+1

Si logger n'est pas un pointeur intelligent (ce qu'il devrait être) alors j'ajouterais aussi 'catch (...) {delete logger; throw;}' Juste au cas où le constructeur CVideoLayer a jeté une autre exception. Si votre objet doit gérer plusieurs ressources (pointeur), il doit alors utiliser des pointeurs intelligents, sinon le constructeur devient très compliqué à implémenter correctement. –

+0

@ Martin, vous avez absolument raison. Réponse mise à jour, merci :) –

1

Une autre tendance est d'utiliser le fait que l'enregistreur est soumis à RAII aussi:

CEngineLayer::CEngineLayer() 
{ 
    CLogger logger(this); // Could throw, but no harm if it does. 
    logger.SetIntent("Creating the video layer!"); 
    videoLayer = new CVideoLayer(this); 
    logger.SetSucceeded(); // resets intent, so CLogger::~CLogger() is silent. 
} 

Cette échelles proprement s'il y a multip les étapes. Vous appelez simplement .SetIntent à plusieurs reprises. Normalement, vous écrivez uniquement la dernière chaîne d'intention dans CLogger::~CLogger() mais pour une journalisation plus détaillée, vous pouvez écrire toutes les intentions.

BTW, dans votre createEngineLayer vous voudrez peut-être un catch(...). Que faire si l'enregistreur lance un DiskFullException?

30

Juste pour répondre à la question (puisque personne ne semble autre avoir répondu), la norme C++ 03 définit std::bad_alloc comme suit:

namespace std { 
    class bad_alloc : public exception { 
    public: 
    bad_alloc() throw(); 
    bad_alloc(const bad_alloc&) throw(); 
    bad_alloc& operator=(const bad_alloc&) throw(); 
    virtual ˜bad_alloc() throw(); 
    virtual const char* what() const throw(); 
    }; 
} 

Puisque la norme définit un constructeur public, vous seriez parfaitement sûr de construire et de jeter un de votre code. (Tout objet avec un constructeur de copie publique peut être lancé, IIRC).

+7

C'est la réponse que je suis venu chercher quand j'ai vu le titre de la question dans ma recherche moteur – d11

5

Je le lance personnellement si j'utilise un allocateur personnalisé dans des conteneurs STL. L'idée est de présenter la même interface - y compris en termes de comportement - aux bibliothèques STL en tant qu'allocation std :: par défaut. Donc, si vous avez un allocateur personnalisé (disons, un allouant à partir d'un pool de mémoire) et que l'allocation sous-jacente échoue, appelez "throw std :: bad_alloc". Cela garantit à l'appelant, dont 99,9999% du temps est un conteneur STL, de le placer correctement. Vous n'avez aucun contrôle sur ce que ces implémentations de STL feront si l'allocateur retourne un gros 0 - il est peu probable que ce soit quelque chose que vous aimerez.