Pourquoi éviter le casting? [fermé]

j'évite généralement les types de moulage autant que possible car j'ai l'impression que c'est une mauvaise pratique de codage et peut entraîner une pénalité de performance.

Mais si quelqu'un m'a demandé d'expliquer pourquoi exactement qui est, je serais probablement regarder comme un cerf dans les phares.

alors pourquoi / quand le casting est-il mauvais?

est-il général pour java, c#, c++ ou est-ce que chaque environnement runtime le gère selon ses propres conditions?

les spécificités de n'importe quelle langue sont les bienvenues, par exemple Pourquoi est-ce mauvais en c++?

93
demandé sur Moshe 2010-11-12 20:19:14

14 réponses

vous avez étiqueté cela avec trois langues, et les réponses sont vraiment très différentes entre les trois. La Discussion du c++ implique plus ou moins la discussion des casts de C aussi, et cela donne (plus ou moins) une quatrième réponse.

puisque c'est celui que vous n'avez pas mentionné explicitement, je vais commencer avec C. C casts ont un certain nombre de problèmes. L'une est qu'ils peuvent faire un certain nombre de choses différentes. Dans certains cas, la distribution ne fait rien de plus que de dire au compilateur (en essence): "la ferme, je sais ce que je fais" -- c'est-à-dire que cela garantit que même lorsque vous faites une conversion qui pourrait causer des problèmes, le compilateur ne vous préviendra pas de ces problèmes potentiels. Par exemple, char a=(char)123456; . Le résultat exact de cette implémentation définie (dépend de la taille et de la signification de char ), et sauf dans des situations assez étranges, n'est probablement pas utile. Les casts C varient également selon qu'il s'agit de quelque chose qui ne se produit qu'au moment de la compilation (c.-à-d. que vous dites simplement compilateur comment interpréter/traiter certaines données) ou quelque chose qui se produit au moment de l'exécution (par exemple, une conversion réelle du double au long).

c++ tente de gérer cela au moins dans une certaine mesure en ajoutant un certain nombre de" nouveaux " opérateurs de distribution, dont chacun est limité à un sous-ensemble des capacités d'une distribution C. Cela rend plus difficile (par exemple) de faire accidentellement une conversion que vous n'aviez vraiment pas l'intention -- si vous seulement intention de lancer loin Constance sur un objet , vous pouvez utiliser const_cast , et être sûr que le seulement chose qu'il peut affecter est si un objet est const , volatile , ou non. Inversement, un static_cast n'est pas autorisé à affecter si un objet est const ou volatile . En bref, vous avez la plupart des mêmes types de capacités, mais ils sont classés de sorte qu'un cast ne peut généralement faire qu'un seul type de conversion, où un seul cast de style C peut faire deux ou trois conversions en une seule opération. L'exception principale est que vous can utiliser un dynamic_cast à la place d'un static_cast dans au moins quelques cas et malgré être écrit comme un dynamic_cast , il finira vraiment par un static_cast . Par exemple, vous pouvez utiliser dynamic_cast pour parcourir une hiérarchie de classe en haut ou en bas -- mais une hiérarchie "en haut" est toujours sûre, donc cela peut être fait statiquement, tandis qu'une hiérarchie "en bas" n'est pas nécessairement sûre, donc c'est fait dynamiquement.

Java et C# sont beaucoup plus similaires. En particulier, avec les deux casting est (virtuellement?) toujours une opération de fonctionnement. En ce qui concerne les opérateurs de cast C++, il est généralement plus proche d'un dynamic_cast en termes de ce qui est vraiment fait -- c.-à-d., quand vous tentez de cast un objet vers un type cible, le compilateur insère une vérification de temps d'exécution pour voir si cette conversion est autorisée, et jeter une exception si ce n'est pas le cas. Les détails exacts (par exemple, le nom utilisé pour le "mauvais casting" exception) varie, mais le principe de base reste essentiellement similaire (bien que, si la mémoire sert, Java ne fait casts appliqués aux quelques Non-objet types comme int beaucoup plus proche de casts C -- mais ces types sont utilisés rarement assez que 1) Je ne me souviens pas que pour sûr, et 2) même si c'est vrai, il n'importe pas beaucoup de toute façon).

en regardant les choses plus généralement, la situation est assez simple (au moins IMO): un plâtre (évidemment assez) signifie que vous convertissez quelque chose d'un type à un autre. Quand/si vous faites cela, cela soulève la question " Pourquoi?"Si vous voulez vraiment que quelque chose soit un type particulier, pourquoi n'avez-vous pas défini ce type pour commencer? Cela ne veut pas dire qu'il ya jamais une raison de faire une telle conversion, mais à tout moment, il se produit, il devrait inviter la question de savoir si vous pouvez re-concevoir le code de sorte que le type correct a été utilisé tout au long. Même des conversions apparemment inoffensives (par exemple, entre entier et flottant point) devrait être examinée de beaucoup plus près que ce qui est commun. En dépit de leur apparence similitude, entiers devraient vraiment être utilisés pour "compté" types de choses et la virgule flottante pour "mesuré" types de choses. Ignorer la distinction est ce qui conduit à certaines des déclarations folles comme "la famille américaine moyenne a 1,8 enfants."Même si nous pouvons tous voir comment cela se produit, le fait est que Non famille a 1,8 enfants. Ils peuvent en avoir 1 ou ils pourraient 2 ou plus -- mais jamais 1.8.

129
répondu Jerry Coffin 2013-08-01 08:36:57

Beaucoup de bonnes réponses ici. Voici la façon dont je le regarde (d'un point de vue C#).

moulage signifie habituellement l'une des deux choses:

  • je connais le type d'exécution de cette expression mais le compilateur ne la connaît pas. Compilateur, je vous le dis, à l'exécution l'objet qui correspond à cette expression va vraiment être de ce type. Maintenant, vous savez que cette expression est d'être traités comme de ce type. Générer du code qui suppose que l'objet sera du type donné, ou, jeter une exception si je me trompe.

  • le compilateur et le développeur connaissent tous deux le type d'exécution de l'expression. Il y a une autre valeur d'un type différent associée à la valeur que cette expression aura à l'exécution. Générez le code qui produit la valeur du type désiré à partir de la valeur du type donné; si vous ne pouvez pas le faire, alors lancez une exception.

Avis que ceux-ci sont les contraires . Il y a deux sortes de moulages! Il ya des casts où vous donnez un indice au compilateur sur réalité - hey, cette chose de type objet est en fait de type client - et il ya des casts où vous dites au compilateur de effectuer une cartographie d'un type à un autre - hey, j'ai besoin de l'int qui correspond à ce double.

les deux sortes de moulages sont des drapeaux rouges. Le premier type de distribution soulève la question "pourquoi exactement Est-ce que le développeur sait quelque chose que le compilateur ne sait pas?"Si vous êtes dans cette situation, alors la meilleure chose à faire est généralement de changer le programme de sorte que le compilateur does avoir un contrôle sur la réalité. Alors vous n'avez pas besoin du plâtre; l'analyse est faite au moment de la compilation.

le deuxième type de cast soulève la question " pourquoi l'opération n'est-elle pas effectuée dans le type de données cible en premier lieu?"Si vous avez besoin d'un résultat en ints, alors pourquoi tenez-vous un double en premier lieu? Tu ne devrais pas tenir un int?

Quelques réflexions supplémentaires ici:

http://blogs.msdn.com/b/ericlippert/archive/tags/cast + opérateur/

42
répondu Eric Lippert 2010-11-12 20:12:49
Les erreurs de moulage

sont toujours rapportées comme des erreurs de temps d'exécution en java. L'utilisation de generics ou de templating transforme ces erreurs en erreurs de compilation, ce qui rend la détection beaucoup plus facile lorsque vous avez fait une erreur.

comme je l'ai dit plus haut. Cela ne veut pas dire que tout le casting est mauvais. Mais s'il est possible de l'éviter, il vaut mieux le faire.

34
répondu Mike Miller 2010-11-12 19:29:24

Casting n'est pas mauvais en soi, c'est juste que c'est souvent utilisée comme un moyen de parvenir à quelque chose qui devrait vraiment pas être fait du tout ou fait de manière plus élégante.

si c'était universellement mauvais, les langues ne le supporteraient pas. Comme toute caractéristique linguistique, elle a sa place.

mon conseil serait de se concentrer sur votre langue principale, et de comprendre tous ses casts, et les meilleures pratiques associées. Qui devrait informer les excursions dans d'autres langues.

à La C# docs sont ici .

il y a un grand résumé sur les options c++ à une question précédente ici .

17
répondu Steve Townsend 2017-05-23 11:53:56

je parle surtout pour C++ ici, mais la plupart de ceci s'applique probablement à Java et C# aussi bien:

C++ est un typé statiquement langue . Il y a quelques passages que le langage vous permet dans ceci (fonctions virtuelles, conversions implicites), mais fondamentalement le compilateur connaît le type de chaque objet au moment de la compilation. La raison d'utiliser un tel langage est que Les erreurs peuvent être détectées au moment de la compilation . Si le compilateur connaît les types de a et b , alors il vous rattrapera au moment de la compilation quand vous faites a=ba est un nombre complexe et b est une chaîne.

chaque fois que vous faites du casting explicite vous dites au compilateur de se taire, parce que vous pensez vous savez mieux . Dans le cas où vous avez tort, vous ne trouverez habituellement qu'à l'exécution . Et le problème quand on le découvre à l'exécution, c'est que c'est peut-être chez un client.

9
répondu sbi 2010-11-12 17:35:21

Java, c# et c++ sont des langages fortement typés, bien que les langages fortement typés puissent être considérés comme inflexibles, ils ont l'avantage de faire une vérification de type au moment de la compilation et de vous protéger contre les erreurs d'exécution qui sont causées par avoir le mauvais type pour certaines opérations.

il y a basically deux types de moulages: un moulage à un type plus général ou un moulage à un autre type (plus spécifique). Casting à un type plus général (casting à un type parent) laissera le temps de compilation des contrôles intacte. Mais le casting vers d'autres types (des types plus spécifiques) désactivera la vérification de type de temps de compilation et sera remplacé par le compilateur par une vérification d'exécution. Cela signifie que vous avez moins de certitude que votre code compilé fonctionnera correctement. Il a également un impact négligeable sur les performances, en raison de la vérification supplémentaire du type d'exécution (L'API Java est pleine de casts!).

5
répondu Kdeveloper 2010-11-17 09:18:06

certains types de coulée sont tellement sûrs et efficaces qu'ils ne sont souvent même pas considérés comme de la fonte.

si vous lancez d'un type dérivé à un type de base, cela est généralement assez bon marché (souvent - en fonction de la langue, de la mise en œuvre et d'autres facteurs - il est à zéro-coût) et est sûr.

si vous moulez d'un type simple comme un int à un type plus large comme un long int, alors encore une fois, il est souvent assez bon marché (généralement pas beaucoup plus cher que attribuer le même type que celui moulé à) et encore est sûr.

D'autres types sont plus lourds et/ou plus chers. Dans la plupart des langues, le casting d'un type de base à un type dérivé est soit bon marché, mais présente un risque élevé d'erreur grave (en C++ si vous static_cast de base à dérivé, il sera bon marché, mais si la valeur sous-jacente n'est pas du type dérivé, le comportement est indéfini et peut être très étrange) ou relativement cher et avec un risque de soulever une exception (dynamic_cast en C++, cast base-to-derived explicite en C#, et ainsi de suite). La boxe en Java et C# en est un autre exemple, et une dépense encore plus importante (étant donné qu'ils changent plus que la façon dont les valeurs sous-jacentes sont traitées).

D'autres types de cast peuvent perdre des informations (Un type entier long à un type entier court).

ces cas de risque (qu'il s'agisse d'une exception ou d'une erreur plus grave) et de dépense sont autant de raisons d'éviter la coulée.

une raison plus conceptuelle, mais peut-être plus importante, est que chaque cas de casting est un cas où votre capacité à raisonner au sujet de l'exactitude de votre code est contrecarrée: chaque cas est un autre endroit où quelque chose peut aller mal, et les façons dont il peut aller mal ajouter à la complexité de déduire si le système dans son ensemble va mal. Même si la distribution est prouvablement sûre à chaque fois, prouver que c'est une partie supplémentaire du raisonnement.

enfin, l'utilisation lourde de plâtre peut indiquer un défaut de considérer le modèle d'objet bien soit dans la création, l'utilisation, ou les deux: Casting-et-vient entre les mêmes types fréquemment est presque toujours un échec à considérer les relations entre les types utilisés. Ici, il n'est pas tellement que les castes sont mauvais, car ils sont un signe de quelque chose de mauvais.

4
répondu Jon Hanna 2010-11-12 17:44:43

il y a une tendance croissante pour les programmeurs à s'accrocher à des règles dogmatiques sur l'utilisation des caractéristiques du langage ("never use XXX!", "XXX considered harmful", etc.), où XXX varie de goto s à des pointeurs à protected data members à des Singleton à des objets passant par la valeur.

suivre de telles règles, dans mon expérience, assure deux choses: vous ne serez pas un programmeur terrible, ni vous serez un grand programmeur.

beaucoup mieux l'approche consiste à creuser et découvrir le noyau de vérité derrière ces interdictions générales, puis utiliser les caractéristiques judicieusement, avec la compréhension qu'il sont de nombreuses situations pour lesquelles ils sont le meilleur outil pour le travail.

"j'en général d'éviter de jeter types autant que possible" en est un bon exemple d'une telle overgeneralized règle. Les moulages sont essentiels dans de nombreuses situations courantes. Quelques exemples:

  1. quand interfonctionnement avec le code tiers (en particulier lorsque ce code est abondant avec typedef s). (Exemple: GLfloat <--> double <--> Real .)
  2. Coulée à partir d'un dérivé de la classe de base pointeur/référence: C'est tellement commun et naturel que le compilateur ne implicitement. Si le rendre explicite augmente la lisibilité, le plâtre est un pas en avant, pas en arrière!
  3. coulée à partir d'une base vers pointeur de classe dérivé/référence: également commun, même en code bien conçu. (Exemple: conteneurs hétérogènes.)
  4. à l'intérieur de la sérialisation/désérialisation binaire ou autre code de bas niveau qui nécessite l'accès aux octets bruts des types intégrés.
  5. tout moment où il est tout simplement plus naturel, pratique, et lisible d'utiliser un type différent. (Exemple: std::size_type --> int .)

Il ya certainement de nombreuses situations où il est pas approprié d'utiliser un plâtre, et il est important d'apprendre ceux-ci aussi; je ne vais pas entrer dans trop de détails puisque les réponses ci-dessus ont fait un bon travail pointant certains d'entre eux.

2
répondu user168715 2010-11-12 22:26:39

pour développer sur réponse de KDeveloper , il n'est pas intrinsèquement sûr de type. Avec casting, il n'y a pas de garantie que ce que vous faites et ce que vous faites concordera, et si cela se produit, vous obtiendrez une exception d'exécution, ce qui est toujours une mauvaise chose.

en ce qui concerne spécifiquement C#, parce qu'il comprend les is et as opérateurs, vous avez la possibilité de (pour les la plupart du temps) de déterminer si une distribution réussirait ou non. Pour cette raison, vous devez prendre les mesures appropriées pour déterminer si l'opération réussira ou non et procéder de manière appropriée.

1
répondu casperOne 2017-05-23 11:53:56

dans le cas de C#, il faut être plus prudent lors du casting à cause des frais généraux de boxe/déboxage impliqués lors du traitement des types de valeurs.

1
répondu Manish Basantani 2010-11-12 17:26:46

Je ne sais pas si quelqu'un l'a déjà mentionné, mais dans C# casting peut être utilisé d'une manière assez sûre, et est souvent nécessaire. Supposons que vous recevez un objet qui peut être de plusieurs types. En utilisant le mot-clé is , vous pouvez d'abord confirmer que l'objet est bien du type vers lequel vous êtes sur le point de le lancer, puis lancer l'objet directement vers ce type. (Je n'ai pas beaucoup travaillé avec Java mais je suis sûr qu'il y a une façon très simple de le faire là aussi).

1
répondu user472875 2010-11-12 18:53:58

vous ne moulez un objet à un type quelconque, si 2 conditions sont remplies:

  1. vous savez qu'il est de ce type
  2. , le compilateur ne fait pas

cela signifie que toutes les informations que vous avez ne sont pas bien représentées dans la structure de type que vous utilisez. C'est mauvais, parce que votre implémentation devrait sémantiquement inclure votre modèle, ce qui n'est clairement pas le cas dans ce cas.

maintenant quand vous faites un plâtre, alors cela peut avoir 2 raisons différentes:

  1. vous avez fait un mauvais travail en exprimant les relations de type.
  2. le système de type de langues n'est tout simplement pas assez expressif pour les formuler.

dans la plupart des langues, vous rencontrez souvent la deuxième situation. Les génériques comme en Java aident un peu, le C++ template system encore plus, mais il est difficile à maîtriser et même alors certaines choses peuvent impossible ou tout simplement pas en vaut la peine.

donc, vous pourriez dire, un plâtre est un piratage sale pour contourner vos problèmes pour exprimer une relation de type spécifique dans une langue spécifique. Les pirouettes doivent être évitées. Mais on ne peut jamais vivre sans eux.

1
répondu back2dos 2010-11-12 19:24:47

en général, les modèles (ou génériques) sont plus sûrs que les modèles casts. À cet égard, je dirais qu'un problème avec la fonte est la sécurité de type. Toutefois, il y a un autre problème plus subtil associé particulièrement à la downcasting: la conception. De mon point de vue au moins, downcasting est une odeur de code, une indication que quelque chose pourrait être mal avec mon desing et je devrais enquêter plus loin. Pourquoi est simple: si vous "obtenez" les bonnes abstractions, vous n'en avez tout simplement pas besoin! Belle question par le façon...

santé!

0
répondu Lefteris Laskaridis 2010-11-12 20:04:27

Pour être vraiment concis, une bonne raison est à cause de la portabilité. Une architecture différente qui s'adapte toutes les deux au même langage peut avoir, par exemple, des encarts de tailles différentes. Donc si je migre D'ArchA à ArchB, qui a un int plus étroit, je pourrais voir un comportement étrange au mieux, et seg faulting au pire.

(je suis clairement en ignorant indépendant de l'architecture du bytecode et IL.)

0
répondu kmarks2 2010-11-12 20:17:42