¿Por qué usar static_cast <int> (x) en lugar de (int) x?

667

Escuché que la static_castfunción debería preferirse al estilo C o al simple estilo de conversión de funciones. ¿Es esto cierto? ¿Por qué?

Tommy Herbert
fuente
36
Objeción su honor, preguntó y respondió .
Graeme Perrow
25
No estoy de acuerdo, esta otra pregunta fue sobre la descripción de las diferencias entre los lanzamientos de lanzamientos en C ++. Esta pregunta trata sobre la utilidad real de static_cast, que es ligeramente diferente.
Vincent Robert
2
Ciertamente podríamos fusionar las dos preguntas, pero lo que tendríamos que preservar de este hilo es la ventaja de usar funciones sobre el casting de estilo C, que actualmente solo se menciona en una respuesta de una línea en el otro hilo, sin votos .
Tommy Herbert
66
Esta pregunta es sobre tipos "incorporados", como int, mientras que esa pregunta es sobre tipos de clase. Parece una diferencia lo suficientemente significativa como para merecer una explicación por separado.
99
static_cast es en realidad un operador, no una función.
ThomasMcLeod

Respuestas:

633

La razón principal es que los moldes clásicos C no hacen distinción entre lo que llamamos static_cast<>(), reinterpret_cast<>(), const_cast<>(), y dynamic_cast<>(). Estas cuatro cosas son completamente diferentes.

A static_cast<>()suele ser seguro. Hay una conversión válida en el lenguaje, o un constructor apropiado que lo hace posible. La única vez que es un poco arriesgado es cuando echas abajo a una clase heredada; debes asegurarte de que el objeto sea realmente el descendiente que afirmas que es, por medios externos al lenguaje (como una bandera en el objeto). A dynamic_cast<>()es seguro siempre que se verifique el resultado (puntero) o se tenga en cuenta una posible excepción (referencia).

A reinterpret_cast<>()(o a const_cast<>()) por otro lado siempre es peligroso. Le dice al compilador: "confía en mí: sé que esto no se parece a un foo(parece que no es mutable), pero lo es".

El primer problema es que es casi imposible saber cuál ocurrirá en un reparto de estilo C sin mirar piezas de código grandes y dispersas y sin conocer todas las reglas.

Asumamos esto:

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

CMyBase  *pSomething; // filled somewhere

Ahora, estos dos se compilan de la misma manera:

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

Sin embargo, veamos este código casi idéntico:

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!!!

Como puede ver, no hay una manera fácil de distinguir entre las dos situaciones sin saber mucho sobre todas las clases involucradas.

El segundo problema es que los modelos de estilo C son demasiado difíciles de localizar. En expresiones complejas puede ser muy difícil ver modelos de estilo C. Es prácticamente imposible escribir una herramienta automatizada que necesite localizar modelos de estilo C (por ejemplo, una herramienta de búsqueda) sin un compilador de C ++ completo. Por otro lado, es fácil buscar "static_cast <" o "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.

Eso significa que, no solo los lanzamientos de estilo C son más peligrosos, sino que es mucho más difícil encontrarlos a todos para asegurarse de que sean correctos.

Euro Micelli
fuente
30
No debe usar static_castpara rechazar una jerarquía de herencia, sino más bien dynamic_cast. Eso devolverá el puntero nulo o un puntero válido.
David Thornley
41
@David Thornley: Estoy de acuerdo, por lo general. Creo que indiqué las advertencias para usar static_casten esa situación. dynamic_castpuede ser más seguro, pero no siempre es la mejor opción. A veces se sabe que un puntero apunta a un subtipo dado, por medio opaco al compilador, y a static_castes más rápido. En al menos algunos entornos, dynamic_castrequiere soporte de compilador opcional y costo de tiempo de ejecución (habilitando RTTI), y es posible que no desee habilitarlo solo para un par de comprobaciones que puede hacer usted mismo. El RTTI de C ++ es solo una posible solución al problema.
Euro Micelli
18
Su afirmación sobre los moldes en C es falsa. Todos los conversiones en C son conversiones de valor, más o menos comparables a C ++ static_cast. El equivalente de C reinterpret_castes *(destination_type *)&, es decir, tomar la dirección del objeto, convertir esa dirección en un puntero a un tipo diferente y luego desreferenciarla. Excepto en el caso de los tipos de caracteres o ciertos tipos de estructuras para los cuales C define el comportamiento de esta construcción, generalmente da como resultado un comportamiento indefinido en C.
R .. GitHub DEJA DE AYUDAR AL HIELO
11
Su buena respuesta aborda el cuerpo de la publicación. Estaba buscando una respuesta al título "por qué usar static_cast <int> (x) en lugar de (int) x". Es decir, para el tipo int(y intsolo), por qué usar static_cast<int>vs. (int)como el único beneficio parece ser con variables de clase y punteros. Solicite que elabore sobre esto.
chux - Restablece a Mónica el
31
@chux, porque int dynamic_castno se aplica, pero todas las otras razones se mantienen. Por ejemplo: digamos que ves un parámetro de función declarado como float, entonces (int)ves static_cast<int>(v). Pero si cambia el parámetro a float*, (int)ven silencio se convierte reinterpret_cast<int>(v), mientras que static_cast<int>(v)es ilegal y correctamente capturado por el compilador.
Euro Micelli
115

Un consejo pragmático: puede buscar fácilmente la palabra clave static_cast en su código fuente si planea ordenar el proyecto.

Karl
fuente
3
También puede buscar utilizando los corchetes, como "(int)", pero es una buena respuesta y una razón válida para usar la conversión de estilo C ++.
Mike
44
@ Mike que encontrará falsos positivos: una declaración de función con un solo intparámetro.
Nathan Osman
1
Esto puede dar falsos negativos: si está buscando una base de código donde no es el único autor, no encontrará modelos de estilo C que otros podrían haber introducido por alguna razón.
Ruslan
77
¿Cómo ayudaría esto a ordenar el proyecto?
Bilow
No buscaría static_cast, porque probablemente sea el correcto. Desea filtrar static_cast, mientras busca reinterpret_cast, const_cast y quizás incluso dynamic_cast, ya que eso indicaría lugares que se pueden rediseñar. C-cast se mezcla todo junto y no te da la razón para el casting.
Dragan
78

En resumen :

  1. static_cast<>() le da una capacidad de comprobación de tiempo de compilación, el elenco de C-Style no.
  2. static_cast<>()se puede detectar fácilmente en cualquier lugar dentro de un código fuente de C ++; en contraste, el molde C_Style es más difícil de detectar.
  3. Las intenciones se transmiten mucho mejor usando moldes C ++.

Más explicaciones :

La conversión estática realiza conversiones entre tipos compatibles . Es similar al elenco de estilo C, pero es más restrictivo. Por ejemplo, el reparto de estilo C permitiría que un puntero entero apunte a un carácter.

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

Dado que esto da como resultado un puntero de 4 bytes que apunta a 1 byte de memoria asignada, escribir en este puntero provocará un error de tiempo de ejecución o sobrescribirá alguna memoria adyacente.

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

A diferencia de la conversión de estilo C, la conversión estática permitirá que el compilador verifique que los tipos de datos de puntero y puntero sean compatibles, lo que permite al programador detectar esta asignación de puntero incorrecta durante la compilación.

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

Lea más sobre:
¿Cuál es la diferencia entre static_cast <> y C estilo casting
y
Regular cast vs. static_cast vs. dynamic_cast

Rika
fuente
21
No estoy de acuerdo con que static_cast<>()sea ​​más legible. Quiero decir, a veces lo es, pero la mayoría de las veces, especialmente en tipos enteros básicos, es horrible e innecesariamente detallado. Por ejemplo: esta es una función que intercambia los bytes de una palabra de 32 bits. Sería casi imposible leer usando static_cast<uint##>()yesos, pero es bastante fácil de entender usando (uint##)yesos. Imagen del código: imgur.com/NoHbGve
Todd Lehman
3
@ToddLehman: Gracias, pero alwaystampoco dije nada . (pero la mayoría de las veces sí) Seguro que hay casos en los que el reparto de estilo c es mucho más legible. Esa es una de las razones por las que el estilo c casting todavía está en vivo y pateando en c ++ imho. :) Por cierto, ese fue un muy buen ejemplo
Rika
8
El código de @ToddLehman en esa imagen usa dos conversiones encadenadas ( (uint32_t)(uint8_t)) para lograr que los bytes además del más bajo se restablezcan. Para eso hay bit a bit y ( 0xFF &). El uso de moldes está ofuscando la intención.
Öö Tiib
28

La pregunta es más grande que simplemente usar wither static_cast o el estilo C porque hay diferentes cosas que suceden cuando se usan los moldes de estilo C. Los operadores de conversión de C ++ están destinados a hacer estas operaciones más explícitas.

En la superficie, las conversiones de estilo static_cast y C aparecen de la misma manera, por ejemplo, al convertir un valor a otro:

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

Ambos arrojan el valor entero a un doble. Sin embargo, cuando se trabaja con punteros, las cosas se vuelven más complicadas. algunos ejemplos:

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

En este ejemplo (1) tal vez está bien porque el objeto señalado por A es realmente una instancia de B. Pero, ¿qué sucede si en ese punto del código no sabe a qué apunta realmente? (2) puede ser perfectamente legal (solo desea mirar un byte del entero), pero también podría ser un error en cuyo caso un error sería bueno, como (3). Los operadores de conversión de C ++ están destinados a exponer estos problemas en el código al proporcionar errores en tiempo de compilación o tiempo de ejecución cuando sea posible.

Por lo tanto, para una "conversión de valor" estricta, puede usar static_cast. Si desea la conversión polimórfica de punteros en tiempo de ejecución, use dynamic_cast. Si realmente quiere olvidarse de los tipos, puede usar reintrepret_cast. Y para lanzar const por la ventana hay const_cast.

Simplemente hacen que el código sea más explícito para que parezca que sabes lo que estabas haciendo.

Dusty Campbell
fuente
26

static_castsignifica que no puedes accidentalmente const_casto reinterpret_cast, lo cual es algo bueno.

DrPizza
fuente
44
Las ventajas adicionales (aunque bastante menores) sobre el reparto de estilo C es que se destaca más (hacer algo potencialmente malo debería verse feo) y es más fácil de usar.
Michael Burr
44
grep-skill es siempre una ventaja, en mi libro.
Branan
7
  1. Permite que los moldes se encuentren fácilmente en su código utilizando grep o herramientas similares.
  2. Hace explícito qué tipo de elenco estás haciendo, y solicita la ayuda del compilador para aplicarlo. Si solo desea deshacerse de la constante, puede usar const_cast, que no le permitirá realizar otros tipos de conversiones.
  3. Los casts son inherentemente feos: usted, como programador, está anulando cómo el compilador trataría su código normalmente. Le estás diciendo al compilador: "Sé mejor que tú". Siendo ese el caso, tiene sentido que realizar un reparto sea algo moderadamente doloroso y que se destaque en su código, ya que son una fuente probable de problemas.

Ver introducción efectiva de C ++

JohnMcG
fuente
Estoy completamente de acuerdo con esto para las clases, pero ¿tiene sentido usar el estilo de C ++ para los tipos de POD?
Zachary Kraus
Creo que sí. Las 3 razones se aplican a los POD, y es útil tener solo una regla, en lugar de otras separadas para las clases y los POD.
JohnMcG
Interesante, podría tener que modificar cómo hago mis lanzamientos en el futuro código para tipos de POD.
Zachary Kraus
7

Se trata de cuánta seguridad de tipografía desea imponer.

Cuando escribe (bar) foo(que es equivalente a reinterpret_cast<bar> foosi no ha proporcionado un operador de conversión de tipo) le está diciendo al compilador que ignore la seguridad de tipo, y simplemente haga lo que se le dice.

Cuando escribe, static_cast<bar> foole está pidiendo al compilador que al menos verifique que la conversión de tipos tenga sentido y, para los tipos integrales, que inserte algún código de conversión.


EDITAR 2014-02-26

Escribí esta respuesta hace más de 5 años, y me equivoqué. (Véanse los comentarios.) ¡Pero todavía recibe votos positivos!

Pitarou
fuente
8
(bar) foo no es equivalente a reinterpret_cast <bar> (foo). Las reglas para "(TYPE) expr" son que elegirá el reparto de estilo C ++ apropiado para usar, que puede incluir reinterpret_cast.
Richard Corden
Buen punto. Euro Micelli dio la respuesta definitiva a esta pregunta.
Pitarou
1
Además, lo es static_cast<bar>(foo), entre paréntesis. Lo mismo para reinterpret_cast<bar>(foo).
LF
6

Los moldes de estilo C son fáciles de perder en un bloque de código. Los modelos de estilo C ++ no solo son una mejor práctica; Ofrecen un mayor grado de flexibilidad.

reinterpret_cast permite conversiones de tipo integral a puntero, sin embargo, puede ser inseguro si se usa incorrectamente.

static_cast ofrece una buena conversión para tipos numéricos, por ejemplo, desde enumeraciones a entradas o entradas a flotantes o cualquier tipo de datos de los que tenga confianza. No realiza ninguna comprobación de tiempo de ejecución.

Dynamic_cast, por otro lado, realizará estas comprobaciones marcando cualquier asignación o conversión ambigua. Solo funciona en punteros y referencias e incurre en una sobrecarga.

Hay un par de otros, pero estos son los principales con los que te encontrarás.

Konrad
fuente
4

static_cast, además de manipular punteros a clases, también se puede usar para realizar conversiones definidas explícitamente en clases, así como para realizar conversiones estándar entre tipos fundamentales:

double d = 3.14159265;
int    i = static_cast<int>(d);
prakash
fuente
44
¿Por qué alguien escribiría static_cast<int>(d), sin embargo, cuando (int)des mucho más conciso y legible? (Quiero decir, en el caso de los tipos básicos, no de punteros a objetos).
Todd Lehman
@ gd1 - ¿Por qué alguien pondría la consistencia por encima de la legibilidad? (en realidad medio serio)
Todd Lehman el
2
@ToddLehman: Yo, considerando que hacer una excepción para ciertos tipos solo porque de alguna manera son especiales para ti no tiene ningún sentido para mí, y tampoco estoy de acuerdo con tu noción de legibilidad. Más corto no significa más legible, como veo en la imagen que publicaste en otro comentario.
gd1
2
static_cast es una decisión clara y consciente de realizar un tipo de conversión muy particular. Por lo tanto, se suma a la claridad de la intención. También es muy útil como marcador para buscar archivos fuente para conversiones en una revisión de código, error o ejercicio de actualización.
Persixty
1
Contrapunto @ToddLehman: ¿Por qué alguien escribiría (int)dcuando int{d}es mucho más legible? La ()sintaxis de constructor, o de función, si la tiene , no es tan rápida para convertirse en un laberinto de paréntesis de pesadilla en expresiones complejas. En este caso, sería en int i{d}lugar de int i = (int)d. Mucho mejor OMI. Dicho esto, cuando solo necesito un temporal en una expresión, uso static_casty nunca he usado moldes de constructor, no lo creo. Solo lo uso (C)castscuando escribo apresuradamente depuración couts ...
underscore_d