J'évite généralement autant que possible le casting de types car j'ai l'impression que c'est une mauvaise pratique de codage et que cela peut entraîner une pénalité de performance.
Mais si quelqu'un me demandait d'expliquer pourquoi exactement, je les regarderais probablement comme un cerf dans les phares.
Alors pourquoi / quand le casting est-il mauvais?
Est-ce général pour java, c #, c ++ ou est-ce que chaque environnement d'exécution le gère selon ses propres conditions?
Les spécificités d'un langage any sont les bienvenues, par exemple pourquoi est-il mauvais en C ++?
Réponses:
Vous l'avez étiqueté avec trois langues, et les réponses sont vraiment très différentes entre les trois. La discussion de C ++ implique plus ou moins une discussion sur les castes en C, ce qui donne (plus ou moins) une quatrième réponse.
Comme c'est celui que vous n'avez pas mentionné explicitement, je vais commencer par C. Les castings ont un certain nombre de problèmes. La première est qu'ils peuvent faire n'importe laquelle de plusieurs choses différentes. Dans certains cas, le casting ne fait rien de plus que de dire au compilateur (en substance): "tais-toi, je sais ce que je fais" - c'est-à-dire qu'il garantit que même lorsque vous effectuez une conversion qui pourrait causer des problèmes, le compilateur ne vous avertira pas de ces problèmes potentiels. Juste par exemple
char a=(char)123456;
,. Le résultat exact de cette implémentation défini (dépend de la taille et de la signature dechar
), et sauf dans des situations plutôt é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'est-à-dire que vous dites simplement au 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 de double à longue).C ++ tente de traiter cela au moins dans une certaine mesure en ajoutant un certain nombre de «nouveaux» opérateurs de conversion, dont chacun est limité à seulement un sous-ensemble des capacités d'un cast C. Cela rend plus difficile (par exemple) de faire accidentellement une conversion que vous n'aviez vraiment pas l'intention - si vous avez seulement l' intention de rejeter la constness sur un objet, vous pouvez l'utiliser
const_cast
, et assurez-vous que la seule chose que cela puisse affecter est de savoir si un objet estconst
,volatile
ou non. Inversement, astatic_cast
n'est pas autorisé à affecter si un objet estconst
ouvolatile
. En bref, vous avez la plupart des mêmes types de capacités, mais elles sont catégorisées de sorte qu'une distribution ne peut généralement effectuer qu'un seul type de conversion, où une seule distribution de style C peut effectuer deux ou trois conversions en une seule opération. La principale exception est que vous pouvez utiliser undynamic_cast
à la place d'unstatic_cast
dans au moins certains cas et bien qu'il soit écrit en tant quedynamic_cast
, il finira vraiment par devenir unstatic_cast
. Par exemple, vous pouvez utiliserdynamic_cast
pour parcourir une hiérarchie de classes vers le haut ou vers le bas - mais une conversion «vers le haut» de la hiérarchie est toujours sûre, donc cela peut être fait de manière statique, tandis qu'une conversion «vers le bas» de la hiérarchie n'est pas nécessairement sûre, donc c'est fait dynamiquement.Java et C # sont beaucoup plus similaires l'un à l'autre. En particulier, avec les deux, la coulée est (pratiquement?) Toujours une opération d'exécution. En termes d'opérateurs de cast C ++, il est généralement plus proche de a
dynamic_cast
en termes de ce qui est vraiment fait - c'est-à-dire que lorsque vous essayez de convertir un objet en un type de cible, le compilateur insère une vérification à l'exécution pour voir si cette conversion est autorisée , et lancez une exception si ce n'est pas le cas. Les détails exacts (par exemple, le nom utilisé pour l'exception de «mauvaise distribution») varient, mais le principe de base reste essentiellement similaire (bien que, si la mémoire estint
bonne , Java effectue des transtypages appliqués aux quelques types non-objets comme beaucoup plus proches de C casts - mais ces types sont assez rarement utilisés pour que 1) je ne m'en souviens pas avec certitude, et 2) même si c'est vrai, cela n'a pas beaucoup d'importance de toute façon).En regardant les choses plus généralement, la situation est assez simple (du moins IMO): un casting (é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 d'un type particulier, pourquoi ne l'avez-vous pas défini comme étant ce type pour commencer? Cela ne veut pas dire qu'il n'y a jamais de raison de faire une telle conversion, mais chaque fois que cela se produit, cela devrait poser la question de savoir si vous pouvez reconcevoir le code pour que le type correct soit utilisé partout. Même les conversions apparemment anodines (par exemple, entre un nombre entier et une virgule flottante) devraient être examinées beaucoup plus attentivement que ce qui est courant. Malgré leur apparencesimilitude, les entiers devraient vraiment être utilisés pour les types de choses «comptés» et la virgule flottante pour les types de choses «mesurées». 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 qu'aucune famille n'a 1,8 enfant. Ils peuvent avoir 1 ou 2 ou ils peuvent en avoir plus - mais jamais 1.8.
la source
Beaucoup de bonnes réponses ici. Voici ma façon de voir les choses (du point de vue C #).
Casting signifie généralement l'une des deux choses suivantes:
Je connais le type d'exécution de cette expression mais le compilateur ne le sait pas. Compilateur, je vous le dis, à l'exécution, l'objet qui correspond à cette expression va vraiment être de ce type. À partir de maintenant, vous savez que cette expression doit être traitée comme étant de ce type. Générez du code qui suppose que l'objet sera du type donné, ou lancez une exception si je me trompe.
Le compilateur et le développeur connaissent le type d'exécution de l'expression. Il existe une autre valeur d'un type différent associée à la valeur que cette expression aura au moment de l'exécution. Générer du code qui produit la valeur du type souhaité à partir de la valeur du type donné; si vous ne pouvez pas le faire, lancez une exception.
Notez que ce sont des opposés . Il existe deux types de moulages! Il y a des casts où vous donnez un indice au compilateur sur la réalité - hé, cette chose de type objet est en fait de type Client - et il y a des casts où vous dites au compilateur d' effectuer un mappage d'un type à un autre - hé, J'ai besoin de l'int qui correspond à ce double.
Les deux types de moulages sont des drapeaux rouges. Le premier type de distribution soulève la question "pourquoi exactement le développeur sait-il quelque chose que le compilateur ne sait pas?" Si vous êtes dans cette situation , alors la meilleure chose à faire est généralement de modifier le programme afin que le compilateur ne possède une poignée sur la réalité. Alors vous n'avez pas besoin du casting; l'analyse est effectuée au moment de la compilation.
Le deuxième type de conversion 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, pourquoi détenez-vous un double en premier lieu? Ne devriez-vous pas tenir un int?
Quelques réflexions supplémentaires ici:
http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/
la source
Foo
objet qui hérite deBar
, et que vous le stockez dans unList<Bar>
, vous aurez besoin de transtypages si vous le souhaitezFoo
. Cela indique peut-être un problème au niveau architectural (pourquoi stockons-nousBar
s au lieu deFoo
s?), Mais pas nécessairement. Et, si cela aFoo
également un cast valideint
, il traite également de votre autre commentaire: vous stockez unFoo
, pas unint
, car anint
n'est pas toujours approprié.Foo
dans unList<Bar>
, mais au moment de la distribution, vous essayez de faire quelque choseFoo
qui n'est pas approprié pour unBar
. Cela signifie que le comportement différent pour les sous-types est effectué via un mécanisme autre que le polymorphisme intégré fourni par les méthodes virtuelles. C'est peut-être la bonne solution, mais le plus souvent, c'est un signal d'alarme.Tuple<X,Y,...>
tuples, il est peu probable que l'utilisation généralisée en C # soit généralisée. C'est un endroit où la langue pourrait faire un meilleur travail pour pousser les gens vers le «gouffre du succès».Les erreurs de diffusion sont toujours signalées comme des erreurs d'exécution dans java. L'utilisation de génériques ou de modèles transforme ces erreurs en erreurs de compilation, ce qui facilite grandement la détection lorsque vous avez commis une erreur.
Comme je l'ai dit plus haut. Cela ne veut pas dire que tout casting est mauvais. Mais s'il est possible de l'éviter, il vaut mieux le faire.
la source
Le casting n'est pas intrinsèquement mauvais, c'est juste qu'il est souvent utilisé à mauvais escient comme un moyen de réaliser quelque chose qui ne devrait vraiment pas être fait du tout ou de manière plus élégante.
S'il était universellement mauvais, les langues ne le prendraient pas en charge. Comme toute autre fonctionnalité du langage, il a sa place.
Mon conseil serait de vous concentrer sur votre langue principale et de comprendre tous ses moulages et les meilleures pratiques associées. Cela devrait informer les excursions dans d'autres langues.
Les documents C # pertinents sont ici .
Il y a un excellent résumé sur les options C ++ lors d'une précédente question SO ici .
la source
Je parle principalement de C ++ ici, mais la plupart de cela s'applique probablement aussi à Java et C #:
C ++ est un langage typé statiquement . Il y a quelques marges de manœuvre que le langage vous permet dans ce domaine (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 des erreurs peuvent être détectées au moment de la compilation . Si le compilateur connaît les types de
a
etb
, il vous attrapera au moment de la compilation lorsque vous faitesa=b
oùa
est un nombre complexe etb
est une chaîne.Chaque fois que vous effectuez un casting explicite, vous dites au compilateur de se taire, car vous pensez que vous savez mieux . Au cas où vous vous tromperiez, vous ne le découvrirez généralement qu'au moment de l'exécution . Et le problème avec la découverte au moment de l'exécution est que cela peut être chez un client.
la source
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 d'effectuer une vérification de type au moment de la compilation et vous protègent contre les erreurs d'exécution causées par le mauvais type pour certaines opérations.
Il existe essentiellement deux types de moulages: un casting vers un type plus général ou un casting vers un autre type (plus spécifique). La conversion vers un type plus général (conversion vers un type parent) laissera les vérifications de la compilation intactes. Mais le cast vers d'autres types (types plus spécifiques) désactivera la vérification du type à la compilation et sera remplacé par le compilateur par une vérification à l'exécution. Cela signifie que vous avez moins de certitude que le 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!).
la source
dynamic_cast
est généralement plus lent questatic_cast
, car il doit généralement effectuer une vérification de type au moment de l'exécution (avec quelques mises en garde: la conversion vers un type de base est bon marché, etc.).Certains types de moulage sont si sûrs et efficaces qu'ils ne sont souvent même pas considérés comme des moulages.
Si vous transtypez d'un type dérivé vers un type de base, cela est généralement assez bon marché (souvent - en fonction de la langue, de l'implémentation et d'autres facteurs - cela ne coûte rien) et est sûr.
Si vous transtypez d'un type simple comme un int vers 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 d'attribuer le même type que celui auquel il est casté) et est encore une fois sûr.
D'autres types sont plus compliqués et / ou plus chers. Dans la plupart des langages, la conversion d'un type de base en un type dérivé est soit bon marché, mais présente un risque élevé d'erreur grave (en C ++, si vous diffusez de manière statique de la base vers un dérivé, elle 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 coûteux et avec un risque de déclencher une exception (dynamic_cast en C ++, conversion explicite de base en dérivée en C #, etc.). La boxe en Java et C # en est un autre exemple, et une dépense encore plus élevée (étant donné qu'elles changent plus que la façon dont les valeurs sous-jacentes sont traitées).
D'autres types de conversion peuvent perdre des informations (un type entier long en un type entier court).
Ces cas de risque (qu'il s'agisse d'une exception ou d'une erreur plus grave) et de frais sont autant de raisons pour éviter le casting.
Une raison plus conceptuelle, mais peut-être plus importante, est que chaque cas de casting est un cas où votre capacité à raisonner sur l'exactitude de votre code est bloquée: chaque cas est un autre endroit où quelque chose peut mal tourner, et la manière dont cela peut se tromper, ajouter à la complexité de déduire si le système dans son ensemble va mal tourner. Même si le casting est prouvé en sécurité à chaque fois, prouver que c'est une partie supplémentaire du raisonnement.
Enfin, l'utilisation intensive des moulages peut indiquer un échec à bien prendre en compte le modèle d'objet, que ce soit lors de sa création, de son utilisation ou des deux: la conversion fréquente entre les mêmes types est presque toujours un échec dans la prise en compte des relations entre les types utilisé. Ici, ce n'est pas tant que les lancers sont mauvais, mais plutôt le signe de quelque chose de mauvais.
la source
Les programmeurs ont de plus en plus tendance à s'accrocher aux règles dogmatiques sur l'utilisation des fonctionnalités du langage ("ne jamais utiliser XXX!", "XXX considéré comme dangereux", etc.), où XXX va de
goto
s aux pointeurs vers lesprotected
données membres en passant par les singletons et les objets passant par valeur.Suivre de telles règles, d'après mon expérience, garantit deux choses: vous ne serez pas un programmeur terrible, ni un grand programmeur.
Une bien meilleure approche consiste à creuser et à découvrir le noyau de vérité derrière ces interdictions générales, puis à utiliser les fonctionnalités judicieusement, en sachant qu'il existe de nombreuses situations pour lesquelles elles sont le meilleur outil pour le travail.
"J'évite généralement autant que possible de lancer des types" est un bon exemple d'une règle aussi généralisée. Les moulages sont essentiels dans de nombreuses situations courantes. Quelques exemples:
typedef
s). (Exemple:GLfloat
<-->double
<-->Real
.)std::size_type
->int
.)Il y a certainement de nombreuses situations où il n'est pas approprié d'utiliser un plâtre, et il est important de les apprendre également; Je n'entrerai pas trop dans les détails car les réponses ci-dessus ont fait du bon travail en signalant certaines d'entre elles.
la source
Pour élaborer sur la réponse de KDeveloper , ce n'est pas intrinsèquement sûr de type. Avec la diffusion, il n'y a aucune garantie que ce que vous lancez et vers lequel vous lancez correspondra, 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 #, car il inclut le
is
as
opérateurs et , vous avez la possibilité (pour la plupart) de déterminer si une conversion réussira ou non. Pour cette raison, vous devez prendre les mesures appropriées pour déterminer si l'opération réussira ou non et se déroulera correctement.la source
Dans le cas de C #, il faut être plus prudent lors du casting en raison des frais généraux de boxe / déballage impliqués lors du traitement des types de valeur.
la source
Je ne sais pas si quelqu'un l'a déjà mentionné, mais en C #, le casting peut être utilisé de manière assez sûre et est souvent nécessaire. Supposons que vous receviez un objet qui peut être de plusieurs types. À l'aide du
is
mot - clé, vous pouvez d'abord confirmer que l'objet est bien du type vers lequel vous êtes sur le point de le convertir, puis convertir directement l'objet en ce type. (Je n'ai pas beaucoup travaillé avec Java, mais je suis sûr qu'il existe également un moyen très simple de le faire).la source
Vous ne transtypez un objet vers un type que si 2 conditions sont remplies:
Cela signifie que toutes les informations dont vous disposez ne sont pas bien représentées dans la structure de type que vous utilisez. C'est mauvais, car votre implémentation doit comprendre sémantiquement votre modèle, ce qui n'est clairement pas le cas dans ce cas.
Maintenant, lorsque vous faites un casting, cela peut avoir 2 raisons différentes:
Dans la plupart des langues, vous vous retrouvez souvent dans la deuxième situation. Les génériques comme Java aident un peu, le système de modèles C ++ encore plus, mais il est difficile à maîtriser et même dans ce cas, certaines choses peuvent être impossibles ou ne pas valoir la peine.
Vous pourriez donc dire qu'un casting est un sale hack pour contourner vos problèmes pour exprimer une relation de type spécifique dans une langue spécifique. Les hacks sales doivent être évités. Mais vous ne pouvez jamais vivre sans eux.
la source
En général, les modèles (ou génériques) sont plus sûrs que les casts. À cet égard, je dirais qu'un problème avec le moulage est la sécurité de type. Cependant, il existe un autre problème plus subtil associé en particulier au downcasting: le design. De mon point de vue au moins, le downcasting est une odeur de code, une indication que quelque chose ne va pas avec ma conception et que je devrais enquêter davantage. Pourquoi est-ce simple: si vous «obtenez» les abstractions correctement, vous n'en avez tout simplement pas besoin! Belle question au fait ...
À votre santé!
la source
Pour être vraiment concis, une bonne raison est la portabilité. Une architecture différente qui accepte tous les deux la même langue peut avoir, par exemple, des ints de tailles différentes. Donc, si je migre d'ArchA vers ArchB, qui a un int plus étroit, je pourrais voir un comportement étrange au mieux, et un seg défaillant au pire.
(J'ignore clairement le bytecode et l'IL indépendants de l'architecture.)
la source