Pourquoi utiliser static_cast <int> (x) au lieu de (int) x?

667

J'ai entendu dire que la static_castfonction devrait être préférée à la coulée de style C ou de style simple. Est-ce vrai? Pourquoi?

Tommy Herbert
la source
36
Objection votre honneur, a demandé et répondu .
Graeme Perrow
25
Je ne suis pas d'accord, cette autre question concernait la description des différences entre les transtypages introduits en C ++. Cette question concerne l'utilité réelle de static_cast, qui est légèrement différente.
Vincent Robert
2
Nous pourrions certainement fusionner les deux questions, mais ce que nous devons préserver de ce fil est l'avantage d'utiliser des fonctions sur le casting de style C, qui n'est actuellement mentionné que dans une réponse d'une ligne dans l'autre fil, sans vote .
Tommy Herbert
6
Cette question concerne les types «intégrés», comme int, tandis que cette question concerne les types de classe. Cela semble être une différence suffisamment importante pour mériter une explication distincte.
9
static_cast est en fait un opérateur, pas une fonction.
ThomasMcLeod

Réponses:

633

La principale raison est que des moulages C classiques ne font aucune distinction entre ce que nous appelons static_cast<>(), reinterpret_cast<>(), const_cast<>()et dynamic_cast<>(). Ces quatre choses sont complètement différentes.

A static_cast<>()est généralement sûr. Il existe une conversion valide dans le langage, ou un constructeur approprié qui le rend possible. La seule fois où c'est un peu risqué, c'est lorsque vous effectuez une conversion vers une classe héritée; vous devez vous assurer que l'objet est bien le descendant que vous prétendez qu'il est, par des moyens externes au langage (comme un drapeau dans l'objet). A dynamic_cast<>()est sûr tant que le résultat est vérifié (pointeur) ou qu'une éventuelle exception est prise en compte (référence).

A reinterpret_cast<>()(ou a const_cast<>()) en revanche est toujours dangereux. Vous dites au compilateur: "croyez-moi: je sais que cela ne ressemble pas à un foo(cela semble ne pas être modifiable), mais il l'est".

Le premier problème est qu'il est presque impossible de dire lequel se produira dans une distribution de style C sans regarder de gros morceaux dispersés et connaître toutes les règles.

Supposons ceci:

class CDerivedClass : public CMyBase {...};
class CMyOtherStuff {...} ;

CMyBase  *pSomething; // filled somewhere

Maintenant, ces deux sont compilés de la même manière:

CDerivedClass *pMyObject;
pMyObject = static_cast<CDerivedClass*>(pSomething); // Safe; as long as we checked

pMyObject = (CDerivedClass*)(pSomething); // Same as static_cast<>
                                     // Safe; as long as we checked
                                     // but harder to read

Voyons cependant ce code presque identique:

CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Compiler error: Can't convert

pOther = (CMyOtherStuff*)(pSomething);            // No compiler error.
                                                  // Same as reinterpret_cast<>
                                                  // and it's wrong!!!

Comme vous pouvez le voir, il n'y a pas de moyen facile de distinguer les deux situations sans en savoir beaucoup sur toutes les classes impliquées.

Le deuxième problème est que les modèles de style C sont trop difficiles à localiser. Dans les expressions complexes, il peut être très difficile de voir les modèles de style C. Il est pratiquement impossible d'écrire un outil automatisé qui doit localiser des transtypages de style C (par exemple un outil de recherche) sans un frontal complet du compilateur C ++. D'un autre côté, il est facile de rechercher "static_cast <" ou "reinterpret_cast <".

pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
      // No compiler error.
      // but the presence of a reinterpret_cast<> is 
      // like a Siren with Red Flashing Lights in your code.
      // The mere typing of it should cause you to feel VERY uncomfortable.

Cela signifie que non seulement les lancers de style C sont plus dangereux, mais qu'il est beaucoup plus difficile de les trouver tous pour s'assurer qu'ils sont corrects.

Euro Micelli
la source
30
Vous ne devez pas utiliser static_castpour abattre une hiérarchie d'héritage, mais plutôt dynamic_cast. Cela renverra soit le pointeur nul, soit un pointeur valide.
David Thornley
41
@David Thornley: Je suis d'accord, généralement. Je pense que j'ai indiqué les mises en garde à utiliser static_castdans cette situation. dynamic_castpeut être plus sûr, mais ce n'est pas toujours la meilleure option. Parfois, vous savez qu'un pointeur pointe vers un sous-type donné, au moyen d'un opaque pour le compilateur, et a static_castest plus rapide. Dans au moins certains environnements, dynamic_castnécessite la prise en charge facultative du compilateur et le coût d'exécution (activation de RTTI), et vous ne voudrez peut-être pas l'activer uniquement pour quelques vérifications que vous pouvez effectuer vous-même. Le RTTI de C ++ n'est qu'une solution possible au problème.
Euro Micelli
18
Votre affirmation concernant les lancers C est fausse. Toutes les conversions C sont des conversions de valeurs, à peu près comparables à C ++ static_cast. L'équivalent C de reinterpret_castest *(destination_type *)&, c'est-à-dire prendre l'adresse de l'objet, transtyper cette adresse en un pointeur vers un type différent, puis déréférencer. Sauf dans le cas des types de caractères ou de certains types de structures pour lesquels C définit le comportement de cette construction, il en résulte généralement un comportement indéfini en C.
R .. GitHub STOP HELPING ICE
11
Votre bonne réponse porte sur le corps du message. Je cherchais une réponse au titre "pourquoi utiliser static_cast <int> (x) au lieu de (int) x". C'est, pour le type int(et intseul), pourquoi l'utilisation static_cast<int>vs (int)comme le seul avantage semble être avec les variables de classe et les pointeurs. Demandez que vous développiez cela.
chux
31
@chux, pour int dynamic_castne s'applique pas, mais toutes les autres raisons sont valables. Par exemple: supposons vqu'un paramètre de fonction soit déclaré comme float, alors (int)vest static_cast<int>(v). Mais si vous modifiez le paramètre sur float*, (int)vdevient silencieusement reinterpret_cast<int>(v)while static_cast<int>(v)est illégal et correctement capturé par le compilateur.
Euro Micelli
115

Une astuce pragmatique: vous pouvez rechercher facilement le mot-clé static_cast dans votre code source si vous prévoyez de ranger le projet.

Karl
la source
3
vous pouvez également effectuer une recherche à l'aide des crochets, comme "(int)", mais une bonne réponse et une raison valable d'utiliser la conversion de style C ++.
Mike
4
@Mike qui trouvera des faux positifs - une déclaration de fonction avec un seul intparamètre.
Nathan Osman
1
Cela peut donner de faux négatifs: si vous recherchez une base de code où vous n'êtes pas le seul auteur, vous ne trouverez pas de transtypages de style C que d'autres auraient pu introduire pour certaines raisons.
Ruslan
7
Comment cela aiderait-il à ranger le projet?
Bilow
Vous ne rechercheriez pas static_cast, car il s'agit probablement de la bonne. Vous voulez filtrer static_cast, pendant que vous recherchez reinterpret_cast, const_cast et peut-être même dynamic_cast, car cela indiquerait des endroits qui peuvent être repensés. C-cast se mélange tous ensemble et ne vous donne pas la raison du casting.
Dragan
78

En bref :

  1. static_cast<>() vous donne une capacité de vérification du temps de compilation, contrairement à la distribution de style C.
  2. static_cast<>()peut être repéré facilement n'importe où dans un code source C ++; en revanche, le casting C_Style est plus difficile à repérer.
  3. Les intentions sont exprimées beaucoup mieux à l'aide de modèles C ++.

Plus d'explication :

La distribution statique effectue des conversions entre les types compatibles . Il est similaire à la distribution de style C, mais est plus restrictif. Par exemple, la conversion de style C permettrait à un pointeur entier de pointer vers un caractère.

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

Étant donné que cela se traduit par un pointeur de 4 octets pointant sur 1 octet de mémoire allouée, l'écriture sur ce pointeur provoquera une erreur d'exécution ou écrasera une partie de la mémoire adjacente.

*p = 5; // run-time error: stack corruption

Contrairement à la distribution de style C, la conversion statique permettra au compilateur de vérifier que les types de données de pointeur et de pointe sont compatibles, ce qui permet au programmeur de détecter cette affectation de pointeur incorrecte lors de la compilation.

int *q = static_cast<int*>(&c); // compile-time error

En savoir plus sur:
Quelle est la différence entre static_cast <> et le casting de style C
et
Cast classique vs static_cast vs dynamic_cast

Rika
la source
21
Je ne suis pas d'accord pour static_cast<>()dire que c'est plus lisible. Je veux dire, parfois c'est le cas, mais la plupart du temps - en particulier sur les types entiers de base - c'est juste horriblement et inutilement verbeux. Par exemple: il s'agit d'une fonction qui permute les octets d'un mot 32 bits. Il serait presque impossible de lire à l'aide de static_cast<uint##>()modèles, mais il est assez facile à comprendre à l'aide de (uint##)modèles. Image du code: imgur.com/NoHbGve
Todd Lehman
3
@ToddLehman: Merci, mais je n'ai pas dit alwaysnon plus. (mais la plupart du temps oui) Dans certains cas, la distribution de style c est beaucoup plus lisible. C'est l'une des raisons pour lesquelles le casting de style c est toujours en direct et donne un coup de pied dans c ++ à mon humble avis. :) Au fait, c'était un très bel exemple
Rika
8
Le code @ToddLehman dans cette image utilise deux transtypages chaînés ( (uint32_t)(uint8_t)) pour obtenir que les octets en plus du plus petit soient réinitialisés. Pour cela, il y a au niveau du bit et ( 0xFF &). L'utilisation de plâtres obscurcit l'intention.
Öö Tiib
28

La question est plus grande que la simple utilisation de with_ static_cast ou de cast de style C car il y a différentes choses qui se produisent lors de l'utilisation de castings de style C. Les opérateurs de transtypage C ++ sont destinés à rendre ces opérations plus explicites.

Sur la surface, les transitions de style static_cast et C semblent avoir la même chose, par exemple lors de la conversion d'une valeur en une autre:

int i;
double d = (double)i;                  //C-style cast
double d2 = static_cast<double>( i );  //C++ cast

Les deux convertissent la valeur entière en double. Cependant, lorsque vous travaillez avec des pointeurs, les choses deviennent plus compliquées. quelques exemples:

class A {};
class B : public A {};

A* a = new B;
B* b = (B*)a;                                  //(1) what is this supposed to do?

char* c = (char*)new int( 5 );                 //(2) that weird?
char* c1 = static_cast<char*>( new int( 5 ) ); //(3) compile time error

Dans cet exemple (1) peut-être OK parce que l'objet pointé par A est vraiment une instance de B. Mais que se passe-t-il si vous ne savez pas à ce stade du code à quoi pointe réellement un? (2) peut-être parfaitement légal (vous ne voulez regarder qu'un octet de l'entier), mais cela pourrait aussi être une erreur, auquel cas une erreur serait bien, comme (3). Les opérateurs de transtypage C ++ sont destinés à exposer ces problèmes dans le code en fournissant des erreurs de compilation ou d'exécution lorsque cela est possible.

Ainsi, pour un "casting de valeur" strict, vous pouvez utiliser static_cast. Si vous voulez une conversion polymorphe au moment de l'exécution des pointeurs, utilisez dynamic_cast. Si vous voulez vraiment oublier les types, vous pouvez utiliser reintrepret_cast. Et pour simplement jeter const par la fenêtre, il y a const_cast.

Ils rendent simplement le code plus explicite afin qu'il semble que vous sachiez ce que vous faisiez.

Dusty Campbell
la source
26

static_castsignifie que vous ne pouvez pas accidentellement const_castou reinterpret_cast, ce qui est une bonne chose.

DrPizza
la source
4
Des avantages supplémentaires (bien que plutôt mineurs) par rapport à la distribution de style C est qu'elle se démarque davantage (faire quelque chose de potentiellement mauvais devrait sembler laid) et qu'elle est plus sensible.
Michael Burr,
4
la capacité de grep est toujours un plus, dans mon livre.
Branan
7
  1. Permet de trouver facilement des transtypages dans votre code à l'aide de grep ou d'outils similaires.
  2. Rend explicite le type de cast que vous faites et engage l'aide du compilateur pour l'appliquer. Si vous souhaitez uniquement supprimer la constance, vous pouvez utiliser const_cast, qui ne vous permettra pas d'effectuer d'autres types de conversions.
  3. Les conversions sont par nature laides - en tant que programmeur, vous annulez la façon dont le compilateur traiterait normalement votre code. Vous dites au compilateur: "Je sais mieux que vous." Cela étant, il est logique que l'exécution d'un casting soit une chose modérément pénible à faire, et qu'ils devraient ressortir de votre code, car ils sont probablement une source de problèmes.

Voir Introduction à C ++ efficace

JohnMcG
la source
Je suis entièrement d'accord avec cela pour les classes, mais est-ce que l'utilisation de cast de style C ++ pour les types POD a un sens?
Zachary Kraus
Je le pense. Les 3 raisons s'appliquent aux POD, et il est utile d'avoir une seule règle, plutôt que des règles distinctes pour les classes et les POD.
JohnMcG
Intéressant, je devrais peut-être modifier la façon dont je fais mes conversions dans le futur code pour les types POD.
Zachary Kraus
7

Il s'agit de la quantité de sécurité que vous souhaitez imposer.

Lorsque vous écrivez (bar) foo(ce qui équivaut à reinterpret_cast<bar> foosi vous n'avez pas fourni d'opérateur de conversion de type), vous dites au compilateur d'ignorer la sécurité de type et faites simplement ce qui est dit.

Lorsque vous écrivez, static_cast<bar> foovous demandez au compilateur de vérifier au moins que la conversion de type est logique et, pour les types intégraux, d'insérer du code de conversion.


EDIT 2014-02-26

J'ai écrit cette réponse il y a plus de 5 ans et je me suis trompé. (Voir commentaires.) Mais il obtient toujours des votes positifs!

Pitarou
la source
8
(bar) foo n'est pas équivalent à reinterpret_cast <bar> (foo). Les règles pour "(TYPE) expr" sont qu'il choisira la conversion de style C ++ appropriée à utiliser, qui peut inclure reinterpret_cast.
Richard Corden
Bon point. Euro Micelli a donné la réponse définitive à cette question.
Pitarou
1
C'est aussi le cas, static_cast<bar>(foo)entre parenthèses. Pareil pour reinterpret_cast<bar>(foo).
LF
6

Les transtypages de style C sont faciles à manquer dans un bloc de code. Les conversions de style C ++ ne sont pas seulement une meilleure pratique; ils offrent une plus grande flexibilité.

reinterpret_cast permet d'intégrer des conversions de type pointeur, mais peut être dangereux s'il est mal utilisé.

static_cast offre une bonne conversion pour les types numériques, par exemple des enums en ints ou des ints en floats ou tout type de données dont vous êtes sûr de taper. Il n'effectue aucune vérification de l'exécution.

dynamic_cast, d'autre part, effectuera ces vérifications en signalant toute affectation ou conversion ambiguë. Il ne fonctionne que sur les pointeurs et les références et entraîne des frais généraux.

Il y en a quelques autres, mais ce sont les principaux que vous rencontrerez.

Konrad
la source
4

static_cast, outre la manipulation de pointeurs vers des classes, peut également être utilisé pour effectuer des conversions explicitement définies dans des classes, ainsi que pour effectuer des conversions standard entre des types fondamentaux:

double d = 3.14159265;
int    i = static_cast<int>(d);
prakash
la source
4
Pourquoi est-ce que quelqu'un écrirait static_cast<int>(d), cependant, quand (int)dest-ce tellement plus concis et lisible? (Je veux dire dans le cas des types de base, pas des pointeurs d'objet.)
Todd Lehman
@ gd1 - Pourquoi quelqu'un mettrait-il la cohérence au-dessus de la lisibilité? (en fait à moitié sérieux)
Todd Lehman
2
@ToddLehman: Moi, considérant que faire une exception pour certains types simplement parce qu'ils sont en quelque sorte spéciaux pour vous n'a aucun sens pour moi, et je suis également en désaccord sur votre notion même de lisibilité. Plus court ne signifie pas plus lisible, comme je le constate sur l'image que vous avez postée dans un autre commentaire.
gd1
2
static_cast est une décision claire et consciente de faire un type de conversion très particulier. Cela ajoute donc à la clarté de l'intention. Il est également très utile comme marqueur pour rechercher des conversions dans les fichiers source lors d'un examen de code, d'un bug ou d'un exercice de mise à niveau.
Persixty
1
@ToddLehman counterpoint: Pourquoi est-ce que quelqu'un écrirait (int)dquand int{d}c'est tellement plus lisible? Constructeur, ou semblable à une fonction si vous en avez (), la syntaxe n'est pas aussi rapide à se transformer en un labyrinthe cauchemardesque de parenthèses dans des expressions complexes. Dans ce cas, ce serait int i{d}au lieu de int i = (int)d. Beaucoup mieux IMO. Cela dit, quand j'ai juste besoin d'un temporaire dans une expression, j'utilise static_castet n'ai jamais utilisé de transtypage constructeur, je ne pense pas. Je n'utilise que (C)castslors de l'écriture précipitée de debug couts ...
underscore_d