Por que usar static_cast(x) en lugar de (int) x?

728

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

9
  • 40
    Objeción su señoría, preguntó y respondió . 19 de septiembre de 2008 a las 16:36
  • 28
    No estoy de acuerdo, esta otra pregunta trataba sobre la descripción de las diferencias entre los lanzamientos introducidos en C ++. Esta pregunta trata sobre la utilidad real de static_cast, que es ligeramente diferente. 19 de septiembre de 2008 a las 16:40
  • 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 la conversión de estilo C, que actualmente solo se menciona en una respuesta de una línea en el otro hilo, sin votos . 20 de septiembre de 2008 a las 8:26
  • 6
    Esta pregunta trata sobre tipos "incorporados", como int, mientras que esa pregunta trata sobre tipos de clases. Parece una diferencia lo suficientemente significativa como para merecer una explicación por separado.
    user153275
    19/04/11 a las 21:21
  • 11
    static_cast es en realidad un operador, no una función. 25 feb 2013 a las 13:47
673

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. Existe 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 se baja 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 compruebe 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 dices al compilador: "confía en mí: sé que esto no parece un foo(parece que no es mutable), pero lo es".

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

Asumamos estos:

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 moldes de estilo C. Es virtualmente imposible escribir una herramienta automatizada que necesite localizar conversiones de estilo C (por ejemplo, una herramienta de búsqueda) sin una interfaz completa del compilador de C ++. 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 todos para asegurarse de que sean correctos.

27
  • 34
    No debe usar static_castpara derribar una jerarquía de herencia, sino más bien dynamic_cast. Eso devolverá el puntero nulo o un puntero válido. 20/01/10 a las 20:18
  • 44
    @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 sabe que un puntero apunta a un subtipo dado, de forma opaca 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 (habilitación de 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. 20/01/10 a las 21:02
  • 21
    Su afirmación sobre los modelos C es falsa. Todas las conversiones de C son conversiones de valor, aproximadamente comparables a C ++ static_cast. El equivalente en C de 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 desreferenciar. Excepto en el caso de tipos de caracteres o ciertos tipos de estructuras para los que C define el comportamiento de esta construcción, generalmente da como resultado un comportamiento indefinido en C. 4 de febrero de 2012 a las 5:56
  • 13
    Tu buena respuesta se dirige al 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), la razón por la que usar static_cast<int>vs. (int)como el único beneficio parece ser con variables de clase y punteros. Solicite que amplíe esto. 15 de diciembre de 2013 a las 2:15
  • 37
    @chux, int dynamic_castno se aplica, pero todas las demás razones son válidas. 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)vsilenciosamente se convierte en reinterpret_cast<int>(v)while static_cast<int>(v)es ilegal y el compilador lo detecta correctamente. 19/12/2013 a las 12:50
121

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

5
  • 4
    también puede buscar usando los corchetes, como "(int)", pero una buena respuesta y una razón válida para usar la conversión de estilo C ++.
    Mike
    7 de ene. De 2014 a las 11:28
  • 8
    @ Mike que encontrará falsos positivos: una declaración de función con un solo intparámetro. 13 de septiembre de 2016 a las 5:22
  • 1
    Esto puede dar falsos negativos: si está buscando una base de código en la que no es el único autor, no encontrará moldes de estilo C que otros podrían haber introducido por algunas razones.
    Ruslan
    9 de junio de 2017 a las 6:46
  • 10
    ¿Cómo ayudaría esto a poner en orden el proyecto?
    Bilow
    10/10/2017 a las 18:40
  • No buscaría static_cast, porque lo más probable es que sea el correcto. Desea filtrar static_cast, mientras busca reinterpret_cast, const_cast y tal vez incluso dynamic_cast, ya que estos indicarían lugares que se pueden rediseñar. C-cast se mezcla todo junto y no te da la razón para el casting.
    Dragan
    18 de mayo de 2020 a las 13:58
91

In short:

  1. static_cast<>() gives you a compile time checking ability, C-Style cast doesn't.
  2. static_cast<>() can be spotted easily anywhere inside a C++ source code; in contrast, C_Style cast is harder to spot.
  3. Intentions are conveyed much better using C++ casts.

More Explanation:

The static cast performs conversions between compatible types. It is similar to the C-style cast, but is more restrictive. For example, the C-style cast would allow an integer pointer to point to a char.

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

Since this results in a 4-byte pointer pointing to 1 byte of allocated memory, writing to this pointer will either cause a run-time error or will overwrite some adjacent memory.

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

In contrast to the C-style cast, the static cast will allow the compiler to check that the pointer and pointee data types are compatible, which allows the programmer to catch this incorrect pointer assignment during compilation.

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

Más información sobre:
¿Cuál es la diferencia entre static_cast <> y el casting de estilo C
y el
reparto regular frente a static_cast frente a dynamic_cast?

3
  • 23
    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 de 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 4 de agosto de 2015 a las 22:17
  • 3
    @ToddLehman: Gracias, pero alwaystampoco dije . (pero la mayoría de las veces sí) Seguramente hay casos en los que el elenco de estilo c es mucho más legible. Esa es una de las razones por las que el casting de estilo c todavía está vivo y funcionando en c ++ en mi humilde opinión. :) Por cierto, fue un muy buen ejemplo.
    Rika
    23 de mayo de 2016 a las 10:06
  • 10
    El código de @ToddLehman en esa imagen usa dos conversiones encadenadas ( (uint32_t)(uint8_t)) para lograr que se restablezcan los bytes además de los más bajos. Para eso hay bit a bit y ( 0xFF &). El uso de yesos está ofuscando la intención. 20/08/18 a las 13:02
29

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

En la superficie, las conversiones de estilo static_cast y C parecen lo mismo, por ejemplo, cuando se lanza un valor a otro:

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

Ambos convierten el valor entero en un doble. Sin embargo, cuando se trabaja con punteros, las cosas se complican más. 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 al que apunta A es realmente una instancia de B. Pero, ¿qué pasa si no sabe en ese punto del código a qué apunta realmente? (2) tal vez perfectamente legal (solo desea ver 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 proporcionando errores en tiempo de compilación o en tiempo de ejecución cuando sea posible.

Por lo tanto, para una "conversión de valor" estricta, puede usar static_cast. Si desea una 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 tirar const por la ventana está const_cast.

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

26

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

2
  • 4
    Las ventajas adicionales (aunque bastante menores) sobre el elenco de estilo C es que se destaca más (hacer algo potencialmente malo debería verse feo) y es más grep-capaz. 19 de septiembre de 2008 a las 16:52
  • 4
    grep-capacidad es siempre una ventaja, en mi libro.
    Branan
    22 de septiembre de 2008 a las 19:15
8
  1. Permite que las conversiones se encuentren fácilmente en su código usando grep o herramientas similares.
  2. Hace que sea explícito qué tipo de reparto está haciendo y solicita la ayuda del compilador para aplicarlo. Si solo desea deshacerse de la const-ness, puede usar const_cast, que no le permitirá hacer otros tipos de conversiones.
  3. Las conversiones son inherentemente feas: usted, como programador, está anulando cómo el compilador normalmente trataría su código. Le está diciendo al compilador: "Yo sé mejor que usted". Siendo ese el caso, tiene sentido que realizar un reparto debería ser una cosa moderadamente dolorosa de hacer, y que deberían sobresalir en su código, ya que son una fuente probable de problemas.

Consulte la introducción eficaz de C ++

3
  • Estoy completamente de acuerdo con esto para las clases, pero ¿tiene algún sentido usar el estilo de C ++ para los tipos de POD? 27 de agosto de 2014 a las 21:44
  • Creo que sí. Las 3 razones se aplican a los POD, y es útil tener solo una regla, en lugar de tener reglas separadas para las clases y los POD.
    JohnMcG
    12/09/2014 a las 19:12
  • Interesante, podría tener que modificar cómo hago mis yesos en el código futuro para los tipos de POD. 14 de septiembre de 2014 a las 3:35
7

Se trata de cuánta seguridad de tipos desea imponer.

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

Cuando escribe, static_cast<bar> foole pide al compilador que al menos compruebe que la conversión de tipos tiene 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é. (Ver comentarios.) ¡Pero todavía recibe votos a favor!

3
  • 8
    (bar) foo no es equivalente a reinterpret_cast <bar> (foo). Las reglas para "(TYPE) expr" son que elegirá el molde de estilo C ++ apropiado para usar, que puede incluir reinterpret_cast. 19 de septiembre de 2008 a las 17:43
  • 1
    Buen punto. Euro Micelli dio la respuesta definitiva a esta pregunta.
    Pitarou
    20 de septiembre de 2008 a las 17:03
  • 1
    Además, lo es static_cast<bar>(foo), entre paréntesis. Lo mismo para reinterpret_cast<bar>(foo).
    L. F.
    25 de julio de 2019 a las 1:58
6

Los moldes de estilo C son fáciles de pasar por alto en un bloque de código. Las conversiones de estilo C ++ no solo son una mejor práctica; ofrecen un grado de flexibilidad mucho mayor.

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, de enumeraciones a ints o ints a flotantes o cualquier tipo de datos del que esté seguro. No realiza comprobaciones de tiempo de ejecución.

dynamic_cast, por otro lado, realizará estas comprobaciones marcando cualquier asignación o conversión ambigua. Solo funciona con punteros y referencias e incurre en gastos generales.

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

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);
5
  • 6
    Sin static_cast<int>(d)embargo, ¿por qué escribiría alguien cuando (int)des mucho más conciso y legible? (Me refiero en el caso de tipos básicos, no punteros a objetos). 4 de agosto de 2015 a las 22:21
  • @ gd1 - ¿Por qué alguien pondría la consistencia por encima de la legibilidad? (en realidad medio en serio) 13/01/16 a las 19:35
  • 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 también estoy en desacuerdo con tu propia noción de legibilidad. Más corto no significa más legible, como veo en la imagen que publicaste en otro comentario.
    gd1
    13/01/16 a las 19:45
  • 2
    static_cast es una decisión clara y consciente de realizar un tipo de conversión muy particular. Por lo tanto, aumenta la claridad de intención. También es muy útil como marcador para buscar conversiones en archivos de origen en una revisión de código, un error o un ejercicio de actualización. 18 de enero de 2016 a las 10:19
  • 1
    Contrapunto de @ToddLehman: ¿Por qué alguien escribiría (int)dcuando int{d}es mucho más legible? Constructor, o similar a una función si lo tiene (), la sintaxis no es tan rápida como para convertirse en un laberinto de pesadilla de paréntesis en expresiones complejas. En este caso, sería en int i{d}lugar de int i = (int)d. Mucho mejor en mi opinión. Dicho esto, cuando solo necesito un temporal en una expresión, uso static_casty nunca he usado conversiones de constructor, no creo. Solo lo uso (C)castscuando escribo apresuradamente debug couts ... 12 de mayo de 2017 a las 23:39