it-swarm-fr.com

Quand un constructeur peut-il lancer une exception?

Quand un constructeur peut-il lancer une exception? (Ou dans le cas de l'Objectif C: quand est-il juste qu'un initié retourne-t-il?)

Il me semble qu'un constructeur devrait échouer - et donc refuser de créer un objet - si cet objet n'est pas complet. Par exemple, le constructeur devrait avoir un contrat avec son appelant pour fournir un objet fonctionnel et fonctionnel sur lequel des méthodes peuvent être appelées de manière significative? Est-ce raisonnable?

197
Mark R Lindsey

Le travail du constructeur consiste à amener l'objet dans un état utilisable. Il existe essentiellement deux écoles de pensée à ce sujet.

Un groupe est favorable à la construction en deux étapes. Le constructeur amène simplement l'objet dans un état dormant dans lequel il refuse de travailler. Il existe une fonction supplémentaire qui effectue l'initialisation réelle.

Je n'ai jamais compris le raisonnement derrière cette approche. Je suis fermement dans le groupe qui prend en charge la construction en une étape, où l'objet est entièrement initialisé et utilisable après la construction.

Les constructeurs à une étape doivent lancer s'ils ne parviennent pas à initialiser complètement l'objet. Si l'objet ne peut pas être initialisé, il ne doit pas être autorisé à exister, le constructeur doit donc lancer.

261
Sebastian Redl

Eric Lippert dit il y a 4 types d'exceptions.

  • Les exceptions fatales ne sont pas de votre faute, vous ne pouvez pas les empêcher et vous ne pouvez pas les éliminer de manière sensée.
  • Les exceptions Boneheaded sont de votre faute, vous auriez pu les éviter et il s’agit donc de bogues dans votre code.
  • Les exceptions frustrantes sont le résultat de décisions de conception malheureuses. Les exceptions vexantes sont levées dans des circonstances tout à fait non exceptionnelles et doivent donc être interceptées et traitées en permanence.
  • Enfin, les exceptions exogènes semblent un peu comme des exceptions vexantes, à la différence qu’elles ne résultent pas de choix de conception malheureux. Ils sont plutôt le résultat de réalités externes peu soignées affectant votre belle logique de programme.

Votre constructeur ne devrait jamais lancer une exception fatale de son propre chef, mais le code qu'il exécute peut provoquer une exception fatale. Quelque chose comme "out of memory" n'est pas quelque chose que vous pouvez contrôler, mais si cela se produit dans un constructeur, hé, ça arrive.

Les exceptions à tête en os ne devraient jamais se produire dans aucun de vos codes, elles sont donc tout à fait correctes.

Les exceptions vexantes (l'exemple Int32.Parse()) ne devraient pas être levées par les constructeurs, car elles ne présentent pas de circonstances non exceptionnelles.

Enfin, les exceptions exogènes doivent être évitées, mais si vous faites quelque chose dans votre constructeur qui dépend de circonstances externes (comme le réseau ou le système de fichiers), il serait approprié de lancer une exception.

Lien de référence: https://blogs.msdn.Microsoft.com/ericlippert/2008/09/10/vexing-exceptions/

56
Jacob Krall

Il n'y a généralement rien à gagner en séparant l'initialisation d'un objet de la construction. RAII est correct, un appel réussi au constructeur doit aboutir à un objet actif entièrement initialisé ou échouer, et ALL échecs à tout moment du code. path devrait toujours lancer une exception. Vous ne gagnez rien en utilisant une méthode init () distincte, sauf une complexité supplémentaire à un certain niveau. Le contrat ctor doit être soit un objet valide valide, soit un objet nettoyé, puis un objet nettoyé.

Considérez si vous implémentez une méthode init séparée, vous toujours devez l’appeler. Il sera toujours possible de lancer des exceptions, de les gérer et de les appeler pratiquement immédiatement après le constructeur, sauf que vous avez maintenant 4 états d'objet possibles au lieu de 2 (IE, construit, initialisé, non initialisé, et échoué vs juste valide et inexistant).

Dans tous les cas, j'ai rencontré 25 ans de OO cas de développement où il semble qu'une méthode d'initialisation distincte permettrait de "résoudre certains problèmes" sont des défauts de conception. Si vous n'avez pas besoin d'un objet MAINTENANT, alors vous ne devriez pas le construire maintenant, et si vous en avez besoin maintenant, vous devez l’avoir initialisé. KISS devrait toujours être le principe suivi, avec le simple concept que le comportement, l'état et l'API de n'importe quelle interface doivent refléter CE QUE fait l'objet, et non COMMENT, le code client ne doit même pas être conscient du fait que l'objet a un type d'état interne qui nécessite une initialisation, ainsi le motif init après viole ce principe.

30
Alhazred

En raison de tous les problèmes qu'une classe partiellement créée peut causer, je dirais jamais.

Si vous devez valider quelque chose pendant la construction, rendez le constructeur privé et définissez une méthode de fabrique statique publique. La méthode peut lancer si quelque chose est invalide. Mais si tout se vérifie, il appelle le constructeur, qui est garanti de ne pas lancer.

6
Michael L Perry

Un constructeur doit lancer une exception lorsqu'il est incapable de terminer la construction dudit objet.

Par exemple, si le constructeur est supposé allouer 1024 Ko de RAM et qu'il échoue, il devrait lever une exception, ainsi l'appelant du constructeur sait que l'objet n'est pas prêt à être utilisé et qu'il y a une erreur. quelque part qui doit être réparé.

Les objets qui sont à moitié initialisés et à moitié morts provoquent simplement des problèmes, car l'appelant n'a aucun moyen de le savoir. Je préférerais que mon constructeur génère une erreur en cas de problème, plutôt que de devoir compter sur la programmation pour exécuter un appel à la fonction isOK () qui renvoie true ou false.

5
Denice

C'est toujours assez risqué, surtout si vous allouez des ressources dans un constructeur; En fonction de votre langue, le destructeur ne sera pas appelé, vous devez donc le nettoyer manuellement. Cela dépend du moment où la vie d'un objet commence dans votre langue.

La seule fois où je l'ai vraiment fait, c'est quand il y a eu un problème de sécurité quelque part, ce qui signifie que l'objet ne devrait pas, ou ne pourrait pas, être créé.

4
blowdart

Il est raisonnable qu'un constructeur lève une exception tant qu'elle se nettoie correctement. Si vous suivez le paradigme RAII (l'initialisation de l'acquisition de ressources est), alors il est assez commun pour un constructeur de faire un travail significatif; un constructeur bien écrit nettoie à son tour s'il ne peut pas être complètement initialisé.

4
Matt Dillard

Autant que je sache, personne ne présente une solution assez évidente qui incarne le meilleur de la construction à une et à deux étapes.

note: Cette réponse suppose C #, mais les principes peuvent être appliqués dans la plupart des langues.

Premièrement, les avantages des deux:

Une étape

La construction en une étape nous profite en empêchant les objets d’exister dans un état non valide, évitant ainsi toute sorte de gestion d’états erronée et tous les bogues qui l’accompagnent. Cependant, certains d'entre nous se sentent étranges parce que nous ne voulons pas que nos constructeurs lèvent des exceptions, et c'est parfois ce que nous devons faire lorsque les arguments d'initialisation sont invalides.

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(dateOfBirth));
        }

        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }
}

Méthode de validation en deux étapes

La construction en deux étapes nous est avantageuse en permettant à notre validation d'être exécutée en dehors du constructeur, et évite ainsi la nécessité d'exécuter des exceptions dans le constructeur. Cependant, cela nous laisse avec des instances "non valides", ce qui signifie qu'il y a un état que nous devons suivre et gérer pour l'instance, ou nous le jetons immédiatement après l'allocation de tas. Cela pose la question suivante: pourquoi effectuons-nous une allocation de tas, et donc une collecte de mémoire, sur un objet que nous n'utilisons même pas?

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public void Validate()
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(Name));
        }

        if (DateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }
    }
}

Single-Stage via un constructeur privé

Alors, comment pouvons-nous garder les exceptions hors de nos constructeurs et nous empêcher de procéder à une allocation de tas sur des objets qui seront immédiatement rejetés? C'est assez basique: nous rendons le constructeur privé et créons des instances via une méthode statique conçue pour effectuer une instanciation, et donc une allocation de tas, uniquement après validation.

public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    private Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public static Person Create(
        string name,
        DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }

        return new Person(name, dateOfBirth);
    }
}

Async Single-Stage via un constructeur privé

Outre les avantages susmentionnés liés à la validation et à la prévention de l'allocation de pile, la méthodologie précédente nous offre un autre avantage intéressant: le support asynchrone. Cela s'avère pratique lorsqu'il s'agit d'une authentification à plusieurs étapes, telle que lorsque vous devez récupérer un jeton porteur avant d'utiliser votre API. Ainsi, vous ne vous retrouvez pas avec un client API "déconnecté" non valide. Vous pouvez simplement recréer le client API si vous recevez une erreur d'autorisation lors de la tentative d'exécution d'une requête.

public class RestApiClient
{
    public RestApiClient(HttpClient httpClient)
    {
        this.httpClient = new httpClient;
    }

    public async Task<RestApiClient> Create(string username, string password)
    {
        if (username == null)
        {
            throw new ArgumentNullException(nameof(username));
        }

        if (password == null)
        {
            throw new ArgumentNullException(nameof(password));
        }

        var basicAuthBytes = Encoding.ASCII.GetBytes($"{username}:{password}");
        var basicAuthValue = Convert.ToBase64String(basicAuthBytes);

        var authenticationHttpClient = new HttpClient
        {
            BaseUri = new Uri("https://auth.example.io"),
            DefaultRequestHeaders = {
                Authentication = new AuthenticationHeaderValue("Basic", basicAuthValue)
            }
        };

        using (authenticationHttpClient)
        {
            var response = await httpClient.GetAsync("login");
            var content = response.Content.ReadAsStringAsync();
            var authToken = content;
            var restApiHttpClient = new HttpClient
            {
                BaseUri = new Uri("https://api.example.io"), // notice this differs from the auth uri
                DefaultRequestHeaders = {
                    Authentication = new AuthenticationHeaderValue("Bearer", authToken)
                }
            };

            return new RestApiClient(restApiHttpClient);
        }
    }
}

Les inconvénients de cette méthode sont peu nombreux, selon mon expérience.

En règle générale, cette méthodologie signifie que vous ne pouvez plus utiliser la classe en tant que DTO, car la désérialisation en un objet sans constructeur public par défaut est difficile, au mieux. Cependant, si vous utilisiez l'objet en tant que DTO, vous ne devriez pas vraiment valider l'objet lui-même, mais plutôt invalider les valeurs de l'objet lorsque vous essayez de les utiliser, car techniquement, les valeurs ne sont pas "invalides" en ce qui concerne au DTO.

Cela signifie également que vous allez créer des méthodes ou des classes fabriques lorsque vous devez autoriser un conteneur IOC) à créer l'objet, sinon le conteneur ne saura pas comment l'instancier. Cependant, dans de nombreux cas, les méthodes d'usine finissent par être l'une des méthodes Create.

4
cwharris

Notez que si vous lâchez une exception dans un initialiseur, vous fuirez si un code utilise le [[[MyObj alloc] init] autorelease] modèle, puisque l'exception ignorera la libération automatique.

Voir cette question:

Comment empêchez-vous les fuites lors de la génération d'une exception dans init?

3
stevex

Si vous écrivez des contrôles d'interface utilisateur (ASPX, WinForms, WPF, ...), évitez de lancer des exceptions dans le constructeur, car le concepteur (Visual Studio) ne peut pas les gérer lorsqu'il crée vos contrôles. Connaissez votre cycle de vie de contrôle (événements de contrôle) et utilisez une initialisation différée dans la mesure du possible.

3
Nick

Voir C++ FAQ sections 17.2 et 17.4 .

En général, j'ai trouvé que le code qui est plus facile à porter et à conserver les résultats si les constructeurs sont écrits pour qu'ils n'échouent pas, et le code qui peut échouer est placé dans une méthode séparée qui renvoie un code d'erreur et laisse l'objet dans un état inerte. .

3
moonshadow

Lancez une exception si vous ne parvenez pas à initialiser l'objet dans le constructeur, par exemple, les arguments illégaux.

En règle générale, une exception doit toujours être levée le plus tôt possible, car elle facilite le débogage lorsque la source du problème se rapproche de la méthode signalant que quelque chose ne va pas.

2
user14070

Vous devez absolument lever une exception d'un constructeur si vous ne parvenez pas à créer un objet valide. Cela vous permet de fournir des invariants appropriés dans votre classe.

En pratique, vous devrez peut-être faire très attention. N'oubliez pas qu'en C++, le destructeur ne sera pas appelé. Par conséquent, si vous lancez après avoir alloué vos ressources, vous devez prendre le plus grand soin de le gérer correctement!

Cette page discute en profondeur de la situation en C++.

2
Luke Halliwell

Je ne peux pas aborder les meilleures pratiques en Objective-C, mais en C++, il est normal qu'un constructeur lève une exception. D'autant qu'il n'y a pas d'autre moyen de s'assurer qu'un état exceptionnel rencontré lors de la construction est signalé sans recourir à la méthode isOK ().

La fonctionnalité block try de fonction a été conçue spécifiquement pour prendre en charge les échecs d'initialisation du constructeur par le membre (bien qu'elle puisse également être utilisée pour des fonctions standard). C'est le seul moyen de modifier ou d'enrichir les informations sur les exceptions qui seront émises. Mais en raison de son objectif initial (utilisation dans les constructeurs), il ne permet pas que l'exception soit avalée par une clause catch () vide.

1
mlbrock

Je ne suis pas sûr que toute réponse puisse être entièrement agnostique à la langue. Certaines langues traitent les exceptions et la gestion de la mémoire différemment.

J'ai déjà travaillé avec des normes de codage exigeant des exceptions, mais uniquement des codes d'erreur sur les initialiseurs, car les développeurs avaient été gravés par le langage qui gérait mal les exceptions. Les langues sans garbage collection gèrent le tas et la pile de manière très différente, ce qui peut être important pour les objets non RAII. Il est toutefois important qu'une équipe décide d'être cohérente afin de savoir par défaut si elle doit appeler des initialiseurs après les constructeurs. Toutes les méthodes (y compris les constructeurs) doivent également être bien documentées quant aux exceptions qu’elles peuvent générer, afin que les appelants sachent les gérer.

Je suis généralement favorable à une construction en une étape, car il est facile d'oublier d'initialiser un objet, mais il existe de nombreuses exceptions à cela.

  • Votre prise en charge linguistique des exceptions n’est pas très bonne.
  • Vous avez une raison de conception pressante pour continuer à utiliser new et delete
  • Votre initialisation nécessite beaucoup de ressources de traitement et doit être exécutée de manière asynchrone sur le thread qui a créé l'objet.
  • Vous créez une DLL) qui peut générer des exceptions en dehors de l'interface avec une application utilisant un langage différent. sont interceptés avant l'interface publique (vous pouvez intercepter des exceptions C++ en C #, mais il y a des obstacles à franchir).
  • Constructeurs statiques (C #)
1
Denise Skidmore

Oui, si le constructeur ne construit pas l'une de ses parties internes, il peut être tenu - par choix - de sa responsabilité de lancer (et dans un certain langage, de déclarer) un exception explicite , dûment noté dans la documentation du constructeur. .

Ce n’est pas la seule option: il pourrait terminer le constructeur et construire un objet, mais avec une méthode 'isCoherent ()' renvoyant la valeur false, afin de pouvoir signaler un état incohérent (cela peut être préférable dans certains cas, afin éviter une interruption brutale du workflow d'exécution en raison d'une exception)
Avertissement: comme l’a dit Eric Chafer dans son commentaire, cela peut apporter une certaine complexité aux tests unitaires (un lancer peut augmenter la complexité cyclomatique de la fonction en raison de la condition qui la déclenche)

S'il échoue à cause de l'appelant (comme un argument null fourni par l'appelant, où le constructeur appelé attend un argument non-nul), le constructeur lève quand même une exception d'exécution non vérifiée.

1
VonC

Lancer une exception pendant la construction est un excellent moyen de rendre votre code plus complexe. Les choses qui semblaient simples deviennent soudainement difficiles. Par exemple, disons que vous avez une pile. Comment ouvrez-vous la pile et renvoyez la valeur supérieure? Eh bien, si les objets de la pile peuvent intégrer leurs constructeurs (construction du temporaire à retourner à l'appelant), vous ne pouvez pas garantir que vous ne perdrez pas de données (pointeur de pile de la décrémentation, construction de la valeur de retour à l'aide du constructeur de copie de valeur dans pile, qui jette, et maintenant une pile qui vient de perdre un objet)! C'est pourquoi std :: stack :: pop ne renvoie pas de valeur et vous devez appeler std :: stack :: top.

Ce problème est bien décrit ici , cochez la case 10, écriture du code exception-sûr.

1
Don Neufeld

Le contrat habituel dans OO) est que les méthodes objet fonctionnent réellement.

Donc, en tant que corrolaire, ne jamais renvoyer un objet zombie forme un constructeur/init.

Un zombie n'est pas fonctionnel et il se peut qu'il manque des composants internes. Juste une exception null-pointeur en attente de se produire.

J'ai d'abord fait des zombies dans l'Objectif C, il y a de nombreuses années.

Comme toutes les règles empiriques, il existe une "exception".

Il est tout à fait possible qu'une interface spécifique ait un contrat stipulant qu'il existe une méthode "initialiser" permettant de transmettre une exception. Le fait qu'un objet implémentant cette interface ne puisse pas répondre correctement à tous les appels, à l'exception des paramètres de propriété, jusqu'à ce que le processus initialize ait été appelé. Je l'ai utilisé pour les pilotes de périphérique dans un OO système d'exploitation pendant le processus de démarrage, et cela était réalisable.

En général, vous ne voulez pas d'objets zombies. Dans des langues comme Smalltalk avec devenir, les choses deviennent un peu pétillantes, mais une surutilisation de , devenir est également un mauvais style. Being permet à un objet de se transformer in situ sur un autre objet, de sorte qu'il n'est pas nécessaire d'utiliser une enveloppe-wrapper (Advanced C++) ou un modèle de stratégie (GOF).

1
Tim Williscroft

La question du PO a une étiquette "indépendante de la langue" ... on ne peut pas répondre de manière sûre à cette question de la même manière pour toutes les langues/situations.

La hiérarchie de classes de l'exemple C # suivant jette dans le constructeur de la classe B en ignorant un appel immédiat à la classe IDisposeable.Dispose à la sortie du using du serveur principal, en sautant explicitement les ressources de la classe A.

Si, par exemple, la classe A avait créé un Socket lors de la construction, connecté à une ressource réseau, tel serait probablement encore le cas après le bloc using (une anomalie relativement cachée).

class A : IDisposable
{
    public A()
    {
        Console.WriteLine("Initialize A's resources.");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose A's resources.");
    }
}

class B : A, IDisposable
{
    public B()
    {
        Console.WriteLine("Initialize B's resources.");
        throw new Exception("B construction failure: B can cleanup anything before throwing so this is not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose B's resources.");
        base.Dispose();
    }
}
class C : B, IDisposable
{
    public C()
    {
        Console.WriteLine("Initialize C's resources. Not called because B throws during construction. C's resources not a worry.");
    }

    public new void Dispose()
    {
        Console.WriteLine("Dispose C's resources.");
        base.Dispose();
    }
}


class Program
{
    static void Main(string[] args)
    {
        try
        {
            using (C c = new C())
            {
            }
        }
        catch
        {           
        }

        // Resource's allocated by c's "A" not explicitly disposed.
    }
}
1
Ashley

Je viens juste d'apprendre Objective C, je ne peux donc pas parler d'expérience, mais j'ai lu des articles à ce sujet dans la documentation d'Apple.

http://developer.Apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_6.html

Cela vous indiquera non seulement comment traiter la question que vous avez posée, mais également l'expliquer.

0
Scott Swezey

Le meilleur conseil que j'ai vu concernant les exceptions est de lever une exception si, et seulement si, l'alternative est le non-respect d'une condition de poste ou le maintien d'un invariant.

Ce conseil remplace une décision subjective peu claire (est-ce un bonne idée) par une question technique précise reposant sur des décisions de conception (conditions invariantes et postérieures) que vous auriez déjà dû prendre.

Les constructeurs sont juste un cas particulier, mais pas spécial, pour ce conseil. La question est donc de savoir quels invariants une classe devrait avoir. Les défenseurs d'une méthode d'initialisation distincte, appelée après la construction, suggèrent que la classe a deux ou plus mode de fonctionnement, avec un mode nready après la construction et au moins un - prêt mode, entré après l'initialisation. C'est une complication supplémentaire, mais acceptable si la classe dispose de toute façon de plusieurs modes de fonctionnement. Il est difficile de voir en quoi cette complication est utile si la classe n’aurait pas autrement de modes de fonctionnement.

Notez que le paramétrage poussé dans une méthode d'initialisation distincte ne vous permet pas d'éviter que des exceptions soient générées. Les exceptions que votre constructeur pourrait avoir levées seront maintenant levées par la méthode d'initialisation. Toutes les méthodes utiles de votre classe devront générer des exceptions si elles sont appelées pour un objet non initialisé.

Notez également qu'éviter la possibilité que votre constructeur génère des exceptions est gênant, et dans de nombreux cas impossible dans de nombreuses bibliothèques standard. En effet, les concepteurs de ces bibliothèques estiment qu’il est judicieux de supprimer des exceptions de constructeurs. En particulier, toute opération qui tente d’acquérir une ressource non partageable ou finie (telle que l’allocation de mémoire) peut échouer et cet échec est généralement indiqué dans les langages et bibliothèques OO) en lançant une exception.

0
Raedwald

Parlant strictement du point de vue Java, chaque fois que vous initialisez un constructeur avec des valeurs illégales, il doit émettre une exception. Ainsi, il ne sera pas construit dans un mauvais état.

0
scubabbl

Pour moi, c'est une décision de conception quelque peu philosophique.

C'est très agréable d'avoir des instances qui sont valables tant qu'elles existent, à partir de maintenant. Dans de nombreux cas non triviaux, cela peut nécessiter de lever des exceptions du cteur si une allocation de mémoire/ressource ne peut pas être faite.

Certaines autres approches sont la méthode init (), qui pose certains problèmes. L'une d'elles est de s'assurer que init () est effectivement appelé.

Une variante utilise une approche paresseuse pour appeler automatiquement init () la première fois qu'un accesseur/mutateur est appelé, mais cela nécessite que tout appelant potentiel se préoccupe de la validité de l'objet. (Par opposition à la "ça existe, donc c'est une philosophie valable").

J'ai déjà vu divers modèles de conception proposés pour traiter ce problème. Par exemple, être capable de créer un objet initial via ctor, mais avoir à appeler init () pour mettre la main sur un objet initialisé contenant des accesseurs/mutateurs.

Chaque approche a ses hauts et ses bas; J'ai utilisé tous ces éléments avec succès. Si vous ne créez pas d'objets prêts à l'emploi à partir du moment où ils ont été créés, je vous recommande une forte dose d'assertions ou d'exceptions pour vous assurer que les utilisateurs n'interagissent pas avant init ().

Addendum

J'ai écrit du point de vue des programmeurs C++. Je suppose également que vous utilisez correctement l'idiome RAII pour gérer les ressources libérées lorsque des exceptions sont générées.

0
nsanders

En utilisant des fabriques ou des méthodes de fabrique pour la création d'objets, vous pouvez éviter les objets non valides sans générer d'exceptions des constructeurs. La méthode de création doit renvoyer l'objet demandé s'il est capable d'en créer un ou null si ce n'est pas le cas. Vous perdez un peu de flexibilité dans la gestion des erreurs de construction chez l'utilisateur d'une classe, car renvoyer null ne vous indique pas ce qui s'est mal passé lors de la création de l'objet. Mais cela évite également d'ajouter la complexité de plusieurs gestionnaires d'exceptions chaque fois que vous demandez un objet, et le risque de capturer des exceptions que vous ne devriez pas gérer.

0
Tegan Mulholland