2009-04-10 6 views
17

Quelle est la meilleure technique pour sortir d'un constructeur sur une condition d'erreur en C++? En particulier, il s'agit d'une erreur lors de l'ouverture d'un fichier.Quelle est la meilleure technique pour sortir d'un constructeur sur une condition d'erreur en C++

Merci pour les réponses. Je lance une exception. Voici le code (ne sais pas si c'est le mieux façon de le faire, mais il est simple)

// Test to see if file is now open; die otherwise 
if (!file.is_open()) { 
    cerr << "Failed to open file: " << m_filename << endl; 
    throw ("Failed to open file"); 
} 

On pense que j'aime C++ est que vous ne devez pas déclarer des exceptions jetées sur les déclarations de méthode .

+0

Je recommanderais de lancer un std :: runtime_error, ou au moins une exception std :: au lieu d'un const char *. – GManNickG

+0

duplication possible de [Comment gérer l'échec dans le constructeur en C++?] (Http://stackoverflow.com/questions/4989807/how-to-handle-failure-in-constructor-in-c) –

Répondre

25

La meilleure suggestion est probablement ce que dit parashift. Mais lisez aussi ma note d'avertissement ci-dessous.

See parashift FAQ 17.2

[17.2] Comment puis-je gérer un constructeur qui échoue?

Lance une exception.

Les constructeurs n'ont pas de type de retour, , il n'est donc pas possible d'utiliser les codes retour . La meilleure façon de signaler erreur constructeur est donc à jeter une exception. Si vous ne disposez pas la possibilité d'utiliser des exceptions, la « moins mauvais » travail autour est de mettre l'objet dans un état « zombie » par la fixation d'un bit d'état interne pour l'objet agit un peu comme il est morts même si techniquement il reste vivant.

L'idée d'un objet "zombie" a un côté négatif de . Vous devez ajouter une fonction membre query ("inspector") à vérifier ce bit "zombie" afin que les utilisateurs de puissent savoir si leur objet est vraiment vivant, ou s'il s'agit d'un zombie (c.-à-d. objet mort vivant), et à peu près tous les lieux construire un de vos objets (y compris dans un objet plus grand ou un tableau d'objets), vous devez vérifier ce drapeau d'état via une instruction if. Vous aurez également envie d'ajouter un à vos fonctions de autres membres: si l'objet est un zombie, faites un no-op ou peut-être quelque chose de plus odieux.

En pratique, la chose "zombie" obtient assez moche. Certainement vous devriez préfèrent les exceptions sur les objets de zombies, mais si vous n'avez pas l'option de en utilisant des exceptions, les objets de zombies pourraient être l'alternative "moins mauvaise".


Un mot d'avertissement à lancer des exceptions dans un constructeur:

Soyez très prudent si parce que si une exception est levée dans un constructeur, le destructeur de la classe n'est pas appelée. Vous devez donc faire attention à la destruction des objets que vous avez déjà construits avant le lancement de l'exception.Les mêmes avertissements s'appliquent au traitement des exceptions en général, mais c'est peut-être un peu moins évident lorsqu'il s'agit d'un constructeur.

class B 
{ 
public: 
    B() 
    { 

    } 

    virtual ~B() 
    { 
     //called after D's constructor's exception is called 
    } 
}; 

class D : public B 
{ 
public: 
    D() 
    { 
     p = new char[1024]; 
     throw std::exception("test"); 
    } 

    ~D() 
    { 
     delete[] p; 
     //never called, so p causes a memory leak 
    } 

    char *p; 
}; 

int main(int argc, char **argv) 
{ 

    B *p; 
    try 
    { 
     p = new D(); 
    } 
    catch(...) 
    { 

    } 


    return 0; 
} 

protégées/constructeurs privés avec méthode CreateInstance:

Une autre façon de contourner cela est de rendre votre privé ou protégé constructeur et faire une méthode CreateInstance qui peut renvoyer des erreurs.

+0

@Neil Butterworth: I J'essaie de me rappeler pourquoi, j'ai déjà eu des problèmes avec un patch de la bibliothèque CLucene, quelque chose impliquant des types dérivés. Cela a conduit à un type partiellement construit qui a provoqué un appel de fonction virtuelle pure pour planter le programme en utilisant CLucene. –

+0

Mis à jour avec la raison, cela peut causer beaucoup de problèmes si votre constructeur ne nettoie pas lui-même avant que l'exception ne soit appelée. –

+0

Désolé - Je pense que tous s'appliquent également à l'utilisation d'un retour d'un constructeur lorsque le destructeur sera appelé sur un objet probablement mal construit, avec des résultats horribles. Dans les deux cas, la réponse consiste à utiliser des objets autogérés en tant que membres de la classe. –

1

Si vous vous opposez après que l'erreur ne puisse pas effectuer ses actions - vous devez lancer. Si cela est possible, connectez-vous une erreur et modifiez la logique de construction.

4

En général, vous devriez lever une exception. L'alternative est d'avoir un objet à moitié construit correctement que l'utilisateur doit tester d'une manière ou d'une autre, ce qu'il échouera inévitablement.

2

Si l'objet que vous construisez n'est pas valide en raison de l'erreur, et doit être éliminé par l'appelant, alors vous devez pratiquement lancer une exception. Cela permet au compilateur d'effectuer la désallocation correcte des ressources. (Ecrire des constructeurs exceptionnellement sûrs nécessite un peu d'attention - en bref, vous devez utiliser les listes d'initialisation partout où vous le pouvez, plutôt que d'utiliser le corps du constructeur - mais c'est critique si vous avez un cas comme celui-ci, où lancer une exception est une possibilité importante.)

+0

Pouvez-vous développer un peu pourquoi il est essentiel d'utiliser les listes d'initialisation lorsqu'une exception peut être levée? Vous semblez suggérer que ce serait intrinsèquement non-exception-à jeter du corps du constructeur, mais je ne suis pas sûr que je vois pourquoi. –

+0

Je ne voulais pas dire qu'un lancer du corps est dangereux. Ce que je voulais dire, c'est que lorsque vous jetez n'importe où dans le ctor (listes de corps ou d'initialiseurs), les agents sont appelés pour tous les champs qui ont été initialisés. Supposons que le lancement se produise pendant un ctor pour un membre de classe qui est lui-même une instance de classe. –

+0

(cnt'd) - le cteur appelant devra détruire tous les champs initialisés, et devra connaître * not * pour détruire tous les champs qui n'ont pas encore été initialisés.Le compilateur s'assure que cela se passe correctement. il est impossible de faire les choses correctement dans tous les cas si vous contournez le compilateur et que vous le faites vous-même. –

0

Il n'y a qu'un seul moyen de quitter un constructeur qui est erroné, c'est-à-dire déclencher une exception.

Est-ce vraiment une erreur? essayez-vous d'ajouter trop au constructeur?

Souvent, les gens essaient de rouler dans une interaction initiale dans le constructeur, comme l'ajout du nom de fichier à un constructeur de fichier. Pensez-vous qu'il ouvrira ce fichier tout de suite ou êtes-vous simplement en train de définir un état, est-ce différent de file.open (filename), est-ce que ça va si ça échoue?

6

Vous pouvez lever une exception, comme d'autres l'ont mentionné, ou vous pouvez également refactoriser votre code afin que votre constructeur ne puisse pas échouer. Si, par exemple, vous travaillez sur un projet où les exceptions sont désactivées ou refusées, cette dernière option est la meilleure.

Pour un constructeur qui ne manquera pas, factoriser le code qui pourrait échouer dans une méthode init(), et ont le constructeur faire aussi peu de travail que possible, et que tous les utilisateurs de la classe d'appeler immédiatement après init() construction. Si init() échoue, vous pouvez renvoyer un code d'erreur. Assurez-vous de documenter cela dans la documentation de votre classe!

Bien sûr, c'est un peu dangereux, car les programmeurs peuvent oublier d'appeler init(). Le compilateur ne peut pas appliquer ceci, donc marchez prudemment, et essayez de rendre votre code défaillant si init() n'est pas appelé.

+0

Un constructeur peut toujours échouer. Tout ce qui doit allouer de la mémoire peut toujours échouer, en particulier dans le type d'environnement qui n'utilisera probablement pas les exceptions. –

+0

J'aimerais savoir comment un constructeur qui ne fait rien, ou qui ne fait qu'initialiser POD peut échouer. –

+0

Il peut échouer sur l'allocation de mémoire. new alloue de la mémoire et l'initialise. En fait, si vous pré-allouer la mémoire et utiliser le placement nouveau, vous pouvez obtenir un constructeur non-jetant. –

0

La meilleure chose à faire est de lancer une exception. C'est ce qu'ils sont là pour, et toute tentative de dupliquer le comportement que vous obtenez est susceptible d'échouer quelque part.

Si vous ne pouvez pas utiliser une exception, pour quelque raison que ce soit, utilisez nothrow. L'exemple de la norme, 18.4.1.1 article 9, est:

t* p2 = new(nothrow) T; // returns 0 if it fails 

Ce qui est techniquement une forme de placement nouvelle, mais il doit soit retourner un objet complètement formé ou un pointeur NULL que vous devez tester, sauf que personne ne le fera.

Si votre classe peut avoir un objet qui est présent mais qui n'a pas été correctement initialisé, vous pouvez avoir un membre de données qui sert de drapeau pour savoir si la classe est utile ou non. Encore une fois, personne ne vérifiera ce drapeau en code direct. Gardez à l'esprit que, si vous avez vraiment besoin d'avoir une allocation garantie pour ne pas échouer, vous devez allouer la mémoire à l'avance et utiliser le placement new, et supprimer toute initialisation qui pourrait lancer à une autre routine, qui quelqu'un ne parviendra pas à appeler. Tout ce qui alloue de la mémoire peut échouer, en particulier sur les types de systèmes les plus confinés qui ne prennent généralement pas en charge les exceptions.

Vraiment, les exceptions sont la meilleure voie à suivre.

Questions connexes