web-dev-qa-db-fra.com

Pourquoi le casting devrait-il être évité?

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

Mais si quelqu'un me demandait d'expliquer pourquoi c'était 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 chaque environnement d'exécution différent le traite-t-il selon ses propres termes?

Les spécificités d'une langue sont les bienvenues, par exemple pourquoi est-il mauvais en c ++?

94
LoudNPossiblyWrong

Vous avez marqué cela en 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 castings C, et cela donne (plus ou moins) une quatrième réponse.

Comme c'est celui que vous n'avez pas mentionné explicitement, je vais commencer par les lancers C. C ont un certain nombre de problèmes. La première est qu'ils peuvent faire un certain nombre de choses différentes. Dans certains cas, le cast ne fait rien de plus que de dire au compilateur (en substance): "tais-toi, je sais ce que je fais" - c'est-à-dire, 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. Par exemple, char a=(char)123456;. Le résultat exact de cette implémentation définie (dépend de la taille et de la signature de char), et sauf dans des situations plutôt étranges, n'est probablement pas utile. Les conversions C varient également selon qu'il s'agit de quelque chose qui se produit uniquement 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 du double en longue).

C++ tente de gérer cela au moins dans une certaine mesure en ajoutant un certain nombre de "nouveaux" opérateurs de transtypage, chacun étant limité à un sous-ensemble des capacités d'un transtypage C. Cela rend plus difficile (par exemple) une conversion accidentelle que vous ne vouliez vraiment pas - si vous seulement avez l'intention de supprimer la constance sur un objet, vous pouvez utiliser const_cast, et assurez-vous que la chose niquement qu'elle peut affecter est de savoir 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 elles sont classées de sorte qu'une seule 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 utilisez un dynamic_cast à la place d'un static_cast dans au moins certains cas et bien qu'il soit écrit comme dynamic_cast, ça va vraiment finir comme static_cast. Par exemple, vous pouvez utiliser dynamic_cast pour parcourir une hiérarchie de classe vers le haut ou vers le bas - mais une remontée "vers le haut" de la hiérarchie est toujours sûre, donc cela peut être fait statiquement, tandis qu'une coulée "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 les uns aux autres. En particulier, avec les deux, la coulée est (pratiquement?) Toujours une opération d'exécution. En ce qui concerne les opérateurs de cast C++, il est généralement le plus proche d'un dynamic_cast en termes de ce qui est vraiment fait - c'est-à-dire, lorsque vous essayez de convertir un objet en un type cible, le compilateur insère une vérification au moment de l'exécution pour voir si cette conversion est autorisée et déclenche une exception dans le cas contraire. Les détails exacts (par exemple, le nom utilisé pour l'exception "bad cast") varient, mais le principe de base reste généralement similaire (bien que, si la mémoire est bonne, Java fait des conversions appliquées à quelques-uns) les types non-objets comme int beaucoup plus proches des transtypages C - mais ces types sont utilisés assez rarement 1) Je ne m'en souviens pas avec certitude, et 2) même si c'est vrai, cela n'a pas d'importance beaucoup de toute façon).

En regardant les choses plus généralement, la situation est assez simple (au 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 y a jamais une 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 afin que le type correct soit utilisé tout au long. Même les conversions apparemment inoffensives (par exemple, entre un entier et une virgule flottante) devraient être examinées de plus près que d'habitude. Malgré leur similitude apparente, les nombres entiers devraient vraiment être utilisés pour les types de choses "comptés" et virgule flottante pour les types de choses "mesurés". Ignorer la distinction est ce qui conduit à certaines 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 la famille no a 1,8 enfants. Ils pourraient avoir 1 ou ils pourraient 2 ou ils pourraient avoir plus que cela - mais jamais 1,8.

135
Jerry Coffin

Les erreurs de transtypage sont toujours signalées comme des erreurs d'exécution en Java. L'utilisation de génériques ou de modèles transforme ces erreurs en erreurs au moment de la compilation, ce qui facilite beaucoup la détection lorsque vous avez fait 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.

35
Mike Miller

Le casting n'est pas intrinsèquement mauvais, c'est juste qu'il est souvent utilisé à mauvais escient comme moyen de réaliser quelque chose qui ne devrait vraiment pas être fait du tout, ou plus élégamment.

S'il était universellement mauvais, les langues ne le supporteraient pas. Comme toute autre fonctionnalité linguistique, elle a sa place.

Mon conseil serait de vous concentrer sur votre langue principale et de comprendre toutes ses transformations 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 SO question ici .

18
Steve Townsend

Je parle surtout pour C++ ici, mais la plupart de cela s'applique probablement à Java = et C # également:

C++ est un langage à typage statique . 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 les erreurs peuvent être détectées au moment de la compilation . Si le compilateur connaît les types de a et b, il vous rattrapera au moment de la compilation lorsque vous le ferez a=ba est un nombre complexe et b est une chaîne.

Chaque fois que vous effectuez un casting explicite, vous dites au compilateur de se taire, car vous pensez vous savez mieux . Si vous vous trompez, vous ne 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 pourrait être chez un client.

9
sbi

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 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 transtypages: un cast vers un type plus général ou un cast vers un autre type (plus spécifique). La conversion vers un type plus général (conversion vers un type parent) laissera les contrôles de temps de compilation intacts. Mais la conversion vers d'autres types (types plus spécifiques) désactivera la vérification du type au moment de la compilation et sera remplacée 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 regorge de transtypages!).

5
Kdeveloper

Certains types de moulages sont si sûrs et efficaces qu'ils ne sont souvent même pas considérés comme des moulages.

Si vous effectuez un cast 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 la mise en œuvre et d'autres facteurs - c'est un coût nul) et est sûr.

Si vous effectuez un cast 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 casté) et encore une fois est sûr.

D'autres types sont plus lourds et/ou plus chers. Dans la plupart des langues, la conversion d'un type de base en un type dérivé est bon marché mais présente un risque élevé d'erreur grave (en C++, si vous effectuez une conversion statique de la base en dérivé, elle sera bon marché, mais si la valeur sous-jacente n'est pas du type dérivé, la le comportement n'est pas défini et peut être très étrange) ou relativement coûteux et avec le risque de déclencher une exception (dynamic_cast en C++, transtypage de base en dérivé explicite en C #, etc.). Boxing in 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 en un type entier court).

Ces cas de risque (d'exception ou d'erreur plus grave) et de dépenses sont autant de raisons d'é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 contrecarrée: chaque cas est un autre endroit où quelque chose peut mal tourner 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 le casting est à chaque fois prouvablement sûr, prouver que c'est une partie supplémentaire du raisonnement.

Enfin, l'utilisation intensive de transtypages peut indiquer une incapacité à bien prendre en compte le modèle d'objet, soit en le créant, en l'utilisant, ou les deux: la conversion fréquente entre les mêmes types est souvent un échec dans la prise en compte des relations entre les types utilisé. Ici, ce n'est pas tant que les lancers sont mauvais, car ils sont le signe de quelque chose de mauvais.

4
Jon Hanna

Il y a une tendance croissante pour les programmeurs à 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 varie de gotos aux pointeurs à protected membres de données à singletons pour passer des objets par valeur.

Suivre de telles règles, selon mon expérience, garantit deux choses: vous ne serez pas un mauvais programmeur, 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 judicieusement les fonctionnalités, étant entendu qu'il y a existe de nombreuses situations pour lesquelles elles sont le meilleur outil pour le travail.

"J'évite généralement les types de casting autant que possible" est un bon exemple d'une telle règle trop généralisée. Les lancers sont essentiels dans de nombreuses situations courantes. Quelques exemples:

  1. Lors de l'interopérabilité avec du code tiers (en particulier lorsque ce code regorge de typedefs). (Exemple: GLfloat <--> double <--> Real.)
  2. Casting à partir d'un pointeur/référence de classe dérivée vers la base: c'est tellement courant et naturel que le compilateur le fera implicitement. Si le rendre explicite augmente la lisibilité, le casting est un pas en avant, pas en arrière!
  3. Conversion d'une base vers un pointeur/référence de classe dérivée: également courant, même dans un code bien conçu. (Exemple: conteneurs hétérogènes.)
  4. Sérialisation/désérialisation binaire interne ou tout autre code de bas niveau qui a besoin d'accéder aux octets bruts des types intégrés.
  5. Chaque fois qu'il est tout simplement plus naturel, pratique et lisible d'utiliser un type différent. (Exemple: std::size_type -> int.)

Il existe certainement de nombreuses situations où il n'est pas approprié d'utiliser un casting, et il est important de les apprendre également; Je n'entrerai pas dans trop de détails car les réponses ci-dessus ont fait du bon travail en en signalant certaines.

2
user168715

Vous ne lancez un objet dans un certain type que si 2 conditions sont remplies:

  1. vous savez que c'est de ce type
  2. le compilateur ne fait pas

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 devrait sémantiquement comprendre votre modèle, ce qui n'est clairement pas le cas dans ce cas.

Maintenant, lorsque vous effectuez un casting, cela peut avoir 2 raisons différentes:

  1. Vous avez mal fait d'exprimer les relations de type.
  2. le système de type des langues n'est tout simplement pas assez expressif pour les exprimer.

Dans la plupart des langues, vous rencontrez souvent la 2ème situation. Les génériques comme dans Java aide un peu, le système de modèles C++ encore plus, mais il est difficile à maîtriser et même alors, certaines choses peuvent être impossibles ou ne valent tout simplement pas l'effort.

Donc, vous pourriez dire, un casting est un hack sale 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.

1
back2dos

Je ne sais pas si quelqu'un l'a déjà mentionné, mais le casting C # peut être utilisé de manière plutôt sûre et est souvent nécessaire. Supposons que vous receviez 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 sur lequel vous êtes sur le point de le caster, puis convertir directement l'objet sur ce type. (Je n'ai pas travaillé avec Java beaucoup mais je suis sûr qu'il y a aussi une manière très simple de le faire là-bas).

1
user472875

Pour élaborer sur réponse de KDeveloper , ce n'est pas intrinsèquement sûr pour le type. Avec le casting, il n'y a aucune garantie que ce à partir duquel vous effectuez le casting et le casting 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 les opérateurs is et as , vous avez la possibilité (pour la plupart) déterminer si un casting 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 procéder correctement.

1
casperOne

Dans le cas de C #, il faut être plus prudent lors du casting en raison des frais généraux de boxe/unboxing impliqués lors du traitement des types de valeur.

1
Manish Basantani

En général, les modèles (ou génériques) sont plus sûrs pour les types que les transtypages. À cet égard, je dirais qu'un problème avec la coulée est la sécurité de type. Cependant, il y a un autre problème plus subtil associé en particulier à la descente: la conception. De mon point de vue au moins, le downcasting est une odeur de code, une indication que quelque chose ne va peut-être pas dans ma conception et que je devrais enquêter davantage. Pourquoi est simple: si vous "obtenez" les abstractions correctement, vous n'en avez tout simplement pas besoin! Belle question au fait ...

À votre santé!

0

Pour être vraiment concis, une bonne raison est due à la portabilité. Des architectures différentes qui acceptent toutes deux la même langue peuvent avoir, par exemple, des tailles différentes. Donc, si je migre d'ArchA vers ArchB, qui a un int plus étroit, je pourrais voir au mieux des comportements étranges, et des défauts seg au pire.

(J'ignore clairement le bytecode indépendant de l'architecture et IL.)

0
kmarks2