8
votes

Meilleure façon de revenir tôt à partir d'une fonction renvoyant une référence

laissez-nous dire que nous avons une fonction du formulaire: xxx

clairement Le code ci-dessus a un problème, si la condition échoue, nous posons un problème sur la manière de revenir de cette fonction . Le creux de ma question est de savoir quelle est la meilleure façon de gérer une telle situation?


16 commentaires

Je pense que cela aiderait à savoir quel type de condition vous parlez. Cela pourrait aider à décider si une exception serait ou non une exception serait une option valide.


La classe dispose de deux ensembles de membres différents et dispose de deux "modes". Retourbject est simplement une vérification des accesseurs et de la somécontion pour voir si le membre qui est demandé est approprié pour le mode donné.


@Whyam, sonne comme cette condition étant fausse indique une erreur logique dans votre code. Donc, ce serait un cas pour affirmer .


@LitB, la somécontion affirme et enregistre si la condition échoue. Cependant, cela ne répond pas à ma question.


Affirmer n'est pas une option. Vous devez lancer ou renvoyer un type nullable (pointeur E.g).


Pourquoi affirmerait-il ne pas être une option? Si une condition préalable est cassée, affirmer est un bon outil pour vous en dire à ce sujet. Si le chemin contenant l'assert n'est pas pris, il n'y a pas de mal. Si le chemin est pris, il ne sert à rien de poursuivre l'exécution du programme.


@Whyamistilltyping: Si vous utilisez une affirmation, cela vous semble que vous (A) devrait être et (B) le faire déjà, vous n'avez pas besoin de renvoyer quoi que ce soit. Vous n'avez pas besoin de ce si (! Somécontion) du tout - il suffit de remplacer tout le bloc complet avec affirmation (somécontion); .


Affirmer n'est pas une option car il doit toujours retourner quelque chose / lancer et car il est supprimé dans la version de version. J'affirme comme ce si (! Cond) {ASSERT (! "Message"); / * Faites quelque chose de concret * /} Assert est une aide de débogage, il ne doit pas être utilisé pour la manipulation des erreurs.


assert est un outil qui vous aide à saisir des erreurs de programmation pendant les tests. Si Somécontion doit toujours contenir dans un logiciel sans bug, alors ! Somécontion doit être affirmé afin que les bugs soient trouvés lors de tests. Si cela pourrait ne pas contenir dans un logiciel sans bug, il ne doit pas être appliqué par un affirmer , mais par d'autres moyens.


Vous ne pouvez pas écrire quelque chose d'illégal juste en mettant un affirmer devant celui-ci. La succursale ! Somécontion doit renvoyer quelque chose de compatible avec le type de retour de la fonction ou lancer une exception.


@David, il n'y a rien d'illégal sur int a [1]; int & f (int n) {assert (n == 0); retourner un [n]; } ... f (1); je pense. En mode de débogage, il se plaindre et en mode libération, l'appelant gagnera le comportement non défini qu'il a demandé :) La question dépend également de la manière dont le code est utilisé. Est-ce utilisé comme bibliothèque? (Je ne pense pas que les bâtonnets de mise en œuvre teste dans opérateur [] de std :: vecteur en mode de version). Ou est-ce que vous savez comment il est utilisé et où les tests en mode de sortie vont bien?


@Johannes, si vous mettez une affirmation pour vérifier si n == 0, vous devez ajouter un test sur n parce que vous savez que c'est un cas d'échec (même si vous ne devriez jamais l'avoir).


Je pense aller avec les termes définis dans blogs.msdn. COM / ERICLIPPERT / Archive / 2008/09/10 / ... , c'est une "exception osseuse" :) on ne l'attrape jamais. Donc, pour moi, cela ressemble à on doit abandonner l'exécution tout de suite au lieu de vous déranger avec le déroulement.


Je pense que c'est aussi la peine de noter les différentes capacités des langues. Où java jette une exception dérivée de erreur , et peut vous montrer une belle trace de pile, pour C ++ si vous laissez l'exception propager il est indéterminé si la pile est réellement déroutée et non spécifiée. Vous risquez de ne pas savoir où l'exception s'est propagée. Pour cette raison, je joue "STOP EXECUTION MAINTENANT, puis BackTrace avec le débogueur" -game.


LitB: Et il y a toujours la possibilité d'utiliser votre propre myASsert macro. Habituellement, c'est juste affirmer , mais lorsque le débogage devient méchant, vous le réécrivez pour piéger dans le débogueur, lancez une exception, tracez une partie de l'état actuel, ou tout ce qui pourrait aider. La comparaison avec Java est APT, je pense - certains programmeurs de niveau supérieur, ne connaissent tout simplement pas la signification des mots ", vous ne devez pas appeler cette fonction à moins que" et vraiment ressentir leurs libertés civiles violées par des fonctions qui ne font que tomber un indice hors limites ou autre chose. Vous devez concevoir votre public attendu ;-)


@David: "Vous ne pouvez pas écrire quelque chose d'illégal simplement en mettant une affirmation devant elle". Non, mais vous pouvez écrire quelque chose d'illégal en documentant ", cette fonction ne doit pas être appelée si ce qui suit serait illégal". Donc, par exemple SHLEN DERREFS son argument sans vérifier pour NULL, et il n'y a rien de mal à cela. Si vous ajoutez également une affirmation, alors franchement, c'est un acte héroïque de la charité envers votre appelant (sans oublier un mouvement intelligent, en considérant que l'appelant est probablement vous et que vous souhaitez trouver vos propres bugs).


14 Réponses :


5
votes

Si vous ne vouliez pas modifier le type de retour sur un pointeur, vous pouvez éventuellement utiliser le NULL modèle d'objet


0 commentaires

5
votes

Quelle est la durée de vie de 'OurObject' et où est-ce créé?

Si vous avez besoin de revenir tôt sans toucher / mettre à jour OurObject et il existe au point où vous retournez, je ne vois pas de problème à y retourner une référence.

Si vous essayez de communiquer qu'il y a eu une erreur, vous devrez probablement lancer une exception au lieu de revenir tôt parce que le contrat que votre fonction a convenu de dire qu'elle renvoie une référence à «SomeObject». / p>

Si vous retournez une référence à un temporaire, vous feriez mieux de réparer que problème ...


4 commentaires

+1 pour avertissement sur les temporaires. Cependant, l'objet est membre de la classe somescope de sorte que ce n'est pas un problème. Bien que l'objet existe, dans certains modes, il est inapproprié d'y accéder (d'où la condition de protection).


Ok, essayez simplement de couvrir toutes les bases. J'ai supposé que OurObject était probablement membre. Dans ce cas, si vous ne pouvez pas accéder à l'objet et que vous ne pouvez pas modifier la signature de fonction pour signaler son inaccessibilité par d'autres moyens, je pense que vous êtes bloqué avec une exception à moins que vous ne puissiez créer une deuxième instance de quelque part qui dit essentiellement " Je ne suis pas valide »et renvoyez une référence à cela.


Hmm, c'est une pensée intéressante. Malheureusement, l'objet en question est un conteneur STL, ce qui n'est pas possible.


Serait-il possible d'avoir un conteneur vide flottant à ces fins? Cela signalerait probablement l'utilisateur qu'il n'y ait rien à voir avec les données. Juste une pensée...



3
votes

euh ... juste jeter une exception ...


5 commentaires

Semble un peu lourds n'est-ce pas? Sans parler de dangereux car chaque appel devrait être enveloppé dans un bloc d'essai.


Je ne voudrais généralement pas jeter une exception, sauf quelque chose d'inattendu est arrivé. Il y a parfois d'autres bons scénarios où il est logique de lancer, mais cela semble comme si ce n'est pas l'un d'entre eux.


Pourquoi les mains lourdes? Et pourquoi les choses devraient-elles être enveloppées dans un bloc d'essai? Si un contrat de fonction indique que sera renvoyer une référence à un objet et que cela ne peut pas alors - presque par définition - c'est une condition exceptionnelle. Une fonction d'appel est libre de faire face à cette exception, mais il n'est pas nécessaire. Il pourrait simplement laisser l'exception propager à une couche capable de le gérer.


En effet, si vous devez envelopper chaque appel dans un bloc d'essai, des exceptions ne sont pas la voie à suivre. Mais vous devriez le signaler dans votre question. La question ne semble pas que somécontion est faux est une condition attendue.


Convenait qu'il est lourd, mais lorsque le type de retour est une référence et que vous ne pouvez pas être modifié, vous n'avez vraiment que deux choix: (1) Utilisez le modèle d'objet NULL comme suggéré par yacoby ci-dessus ou (2) lancer une exception. Dans tous les cas, si le type de retour est du tout malléable, j'explore certainement le modifier à un pointeur et passer du temps à justifier pourquoi c'est une référence.



3
votes

lancer une exception


3 commentaires

-1: Je ne voudrais généralement pas jeter une exception, sauf quelque chose d'inattendu est arrivé. Il y a parfois d'autres bons scénarios où il est logique de lancer, mais cela semble comme si ce n'est pas l'un d'entre eux.


Non Ceci est un exemple où l'exception est le meilleur choix (à côté de la modification du type de valeur de retour ou de renvoyer une valeur par défaut). Les fonctions qui renvoient référence doivent renvoyer une référence ou lancer une exception.


Adrian: Si lancez une exception n'est pas le meilleur choix que la fonction ne doit pas renvoyer une référence. Demandez-lui que quelque chose d'autre ou transmettez une référence non-const à la fonction à modifier.



13
votes

J'utiliserais un pointeur au lieu d'une référence dans ce cas. En fait, ce critère (valeur de retour optionnel ou obligatoire) est de savoir comment je décide entre les pointeurs et les références en premier lieu.


0 commentaires

1
votes

Je vais envisager de retourner en référence uniquement si je suis sûr que la fonction n'échouera jamais (probablement de renvoyer une référence de const à une variable de membre interne). S'il existe plusieurs validations dans la fonction, je vais considérer l'une des options suivantes:

  • si la copie de l'objet n'est pas coûteux, retourner une copie
  • S'il est coûteux de copier, renvoyez un const-pointeur. Retour null en cas de échec.

0 commentaires

5
votes

Je retournerais un booléen et passez la référence d'objet comme paramètre sur la fonction: xxx

Dans ce cas, votre objet n'a maintenant été rempli que si la fonction retourne vrai.


3 commentaires

Intéressant, je pensais moi-même dans ces lignes.


J'aime cette réponse. Le drapeau vous indique si l'objet transmis a été modifié.


Cela ne fait pas la même chose que le code d'origine, cependant. Le code d'origine renvoie une référence à OurObject (et de modifications futures à OurObject, via des références non-Const, seront visibles via la référence de l'appelant). Cela fait une copie de OurObject, comme au moment où la fonction est appelée. Ni est nécessairement juste ou faux, mais ils ont des objectifs différents.



24
votes

Ce n'est pas une question syntaxique, mais une question de conception. Vous devez spécifier ce que retourObject () est censé revenir quand somécontion est vrai. Cela dépend principalement de ce que la fonction va être utilisée. Et que vous ne nous avez pas dit.

Selon les problèmes de conception, je vois quelques façons syntaxiques possibles en dehors de ceci:

  • renvoie une référence à un autre objet; Vous devriez avoir un objet ersatz quelque part
  • avoir un objet spécial "no-object à retourner" quelque part que vous retournez une référence à; Les clients peuvent vérifier cela; S'ils ne vérifient pas, ils obtiennent un comportement par défaut raisonnable
  • renvoie un pointeur, pas une référence; Les clients devraient toujours vérifier la valeur de retour de la fonction
  • lancer une exception; Si Somécontion est quelque chose d'exceptionnel quels clients ne peuvent pas traiter de ce qui serait approprié
  • affirment; Si Somécontion devrait toujours tenir, il devrait être affirmé

1 commentaires

Lorsque le C ++ renvoie des références, il s'agit généralement de références implicites aux objets contenus ou au présent pointeur (auto) - simplement à cause de ce problème. Lors du retour des références aux objets, vous devez savoir que vous avez construit un objet valide. Les références sont en quelque sorte inutiles autrement.



1
votes

Si vous ne voulez pas jeter une exception, vous pouvez essayer quelque chose comme:

const SomeObject& GetSomeObject(const SomeObject& default_if_not_found = SomeObject())
{
    // ...
    if (!found)
        return default_if_not_found;

    return ourObject;
}


2 commentaires

Quelle est la durée de vie d'un argument temporaire utilisé comme argument par défaut?


Les temporaires liés à Const-Références ont la même vie que la référence constante qu'ils sont liées. Je suppose que si le temporaire est ensuite retourné, il gagne la durée de vie de la référence de const recevoir le retour de la fonction - ce qui est pratique.



0
votes

Je crois que la réponse dépend de la question de savoir si vous êtes le genre de programmeur qui considère comme mauvais et ne veut jamais les utiliser ou non. Si vous les utilisez, et c'est vraiment une condition exceptionnelle, une exception doit être lancée. Oui, les utilisateurs doivent y faire face, mais cacher une erreur pour prétendre que rien n'est arrivé n'est tout simplement pas une option. Mais si vous n'utilisez jamais d'exceptions, rendez-vous simplement un pointeur ou un booléen et ajoutez une référence non constituée à votre fonction. Les deux solutions sont simples, mais qui ont dit que les choses trop complexes rendent le code mieux?


0 commentaires

6
votes

Je ferais probablement cela: xxx

et peut-être aussi: xxx

Les appelants ont alors quelques options: xxx

Vous concevez une interface de la fonction différemment en fonction de ce que sont les conditions préalables, c'est-à-dire dont la responsabilité est de s'assurer qu'elle peut faire son travail.

Si c'est la responsabilité de l'appelant, Ensuite, ils ont besoin de la capacité de s'assurer que cela. Vous voudrez peut-être vérifier quand même, juste pour être du côté sûr. Jeter une exception lorsque les «conditions préalées» ne sont pas remplies les rendent plus comme des conseils que les conditions, et cela peut fonctionner assez bien à condition que cela ne vous dérange pas des frais de contrôle des chèques d'exécution partout. Je suis généralement assez impitoyable avec des conditions préalables, je pourrais donc remplacer l'exception conditionnelle avec une affirmation et documenter que la fonction ne doit pas être appelée en mode incorrect. Cela dépend de la quantité de contrôle de l'appelant sur le mode - évidemment s'il change de manière arbitraire (par exemple, si "la somécontion" est "il n'y a pas d'octet disponible sur le UART"), vous avez besoin d'une interface très différente de si elle ne change que jamais Lorsque le code client appelle une fonction pour le modifier.

Si ce n'est pas la responsabilité de l'appelant d'obtenir le mode droit, votre interface ne doit pas écrire des chèques que votre implémentation ne peut pas encaisser. Dans ce cas s'il est "attendu" qu'il n'y aura aucun objet de retour, le retour ne devrait pas être par référence. Sauf indication contraire (A), vous pouvez franchir une spéciale "désolé, il n'a pas fonctionné" objet à retourner, que l'appelant peut vérifier, ou (b) vous êtes à l'aise de lancer des exceptions dans les situations "attendues". IMO (B) est rare en C ++. (a) n'est généralement pas mieux que de retourner un pointeur NULL, mais de temps en temps. Par exemple, retourner une collection vide pour signifier "Il n'y a rien que vous êtes autorisé à voir ici" peut entraîner un code d'appel très propre, si DosomeLeelse () aurait été "ne rien faire". Donc, ainsi que les responsabilités souhaitées, l'interface dépend des cas d'utilisation attendus.


1 commentaires

+1 Pour une bonne explication et prenant le temps de fournir des exemples clairs.



0
votes

La manière dont la fonction est configurée, vous devez retourner un TooOObject ou jeter une exception. Si vous voulez faire autre chose, vous devez modifier la fonction. Il n'y a pas d'autres choix.

si somécontion ne sera jamais faux, vous pouvez supprimer le test dans le code et mettre un affirmer dans ou vous pouvez garder le test et lancer une exception. . (Je suis répugnant de recommander de ne pas en tenir compte de la possibilité, car "jamais fausse" signifie presque toujours "Jamais faux, à moins que quelque chose de mauvais soit arrivé et non détecté", et si cela vaut la peine d'être testé pendant toute condition de détection.)

Il y a la possibilité de retourner une sorte d'objet nul. Les pointeurs sont utiles pour cela, car il existe une valeur null évidente qui teste False, mais vous pouvez transmettre un drapeau en tant que paramètre OUT ou renvoyer un objet spécial destiné à une null. Ensuite, vous devrez tester la valeur de retour sur chaque appel pour que cela soit significatif, et ce n'est pas probablement moins tracas que le affirmer ou lancer . < / p>

Si je faisais cela, cela dépend de Somécontion . S'il a des effets secondaires ou n'est pas difficile de calculer, de tester et de lancer une exception si faux. Si c'est onéreuse de calculer, utilisez affirmer de sorte que vous ne devez pas le faire en production et l'oublier à côté de cela.


0 commentaires

1
votes

exactement trois choix ...

1. lancer une exception
2. retourne un objet d'erreur factice
3. Utilisez un pointeur et retournez null


0 commentaires

0
votes

Ceci est la même situation que l'opérateur C ++ intégré dynamic_cast <> . Il peut être coulé à un type de pointeur ou à un type de référence. Lorsqu'il s'agit d'un type de pointeur, il renvoie null sur une défaillance. Lorsqu'il s'agit d'un type de référence, il jette l'exception std :: bad_cast sur l'échec. Suite au même exemple, vous lanceriez une exception.


0 commentaires