2010-03-04 17 views
11

AFAIK, para punteros/referencias static_cast, si una definición de clase no es visible para el compilador en este punto, entonces static_cast se comportará como reinterpret_cast.static_cast safety

¿Por qué static_cast no es seguro para punteros/referencias y es seguro para valores numéricos?

Respuesta

22

En resumen, debido a la herencia múltiple.

En larga:

#include <iostream> 

struct A { int a; }; 
struct B { int b; }; 
struct C : A, B { int c; }; 

int main() { 
    C c; 
    std::cout << "C is at : " << (void*)(&c) << "\n"; 
    std::cout << "B is at : " << (void*)static_cast<B*>(&c) << "\n"; 
    std::cout << "A is at : " << (void*)static_cast<A*>(&c) << "\n"; 

} 

Salida:

C is at : 0x22ccd0 
B is at : 0x22ccd4 
A is at : 0x22ccd0 

Tenga en cuenta que el fin de convertir correctamente a B *, static_cast tiene que cambiar el valor del puntero. Si el compilador no tuviera la definición de clase para C, entonces no sabría que B era una clase base, y ciertamente no sabría qué compensación aplicar.

Pero en esa situación en la que no hay una definición es visible, static_cast no se comporta como reinterpret_cast, está prohibido:

struct D; 
struct E; 

int main() { 
    E *p1 = 0; 
    D *p2 = static_cast<D*>(p1); // doesn't compile 
    D *p3 = reinterpret_cast<D*>(p1); // compiles, but isn't very useful 
} 

Un molde sencillo de estilo C, (B*)(&c) hace lo que dice: si la definición de la estructura C es visible, mostrando que B es una clase base, luego es lo mismo que un static_cast. Si los tipos solo son forward-declarated, entonces es lo mismo que reinterpret_cast. Esto se debe a que está diseñado para ser compatible con C, lo que significa que tiene que hacer lo que hace C en los casos que son posibles en C.

static_cast siempre sabe qué hacer para los tipos incorporados, eso es realmente lo que tiene incorporado medio. Puede convertir int a float, y así sucesivamente. Por eso siempre es seguro para los tipos numéricos, pero no puede convertir los punteros a menos que (a) sepa a qué apuntan, y (b) existe el tipo correcto de relación entre los tipos apuntados. Por lo tanto, puede convertir int en float, pero no int* en float*.

Como dice AndreyT, hay una manera que puede utilizar static_cast manera insegura, y el compilador probablemente no te salvará, porque el código es legal:

A a; 
C *cp = static_cast<C*>(&a); // compiles, undefined behaviour 

Una de las cosas que puede hacer es static_cast "downcast" un puntero a una clase derivada (en este caso, C es una clase derivada de A). Pero si la referencia no es realmente de la clase derivada, estás condenado. Un dynamic_cast realizaría una comprobación en tiempo de ejecución, pero para mi clase de ejemplo C no puede usar un dynamic_cast, porque A no tiene funciones virtuales.

También puede hacer cosas inseguras con static_cast ay desde void*.

+0

La herencia múltiple no es el único problema, la respuesta de AndreyT aborda el problema de la transmisión descendente al tipo incorrecto. –

+0

Cierto, solo contesté completamente el primer párrafo de la pregunta, y parcialmente el segundo párrafo. No es el problema general (en el título) de la seguridad static_cast. –

5

No, su "AFAIK" es incorrecto. static_cast nunca se comporta como reinterpret_cast (excepto, tal vez cuando se convierte a void *, aunque normalmente esta conversión no se lleva a cabo por reinterpret_cast).

primer lugar, cuando static_cast se utiliza para el puntero o una referencia conversiones, la especificación de la static_cast requiere explícitamente una cierta relación de existir entre los tipos (y ser conocido a static_cast).Para los tipos de clase, se relacionarán por herencia, según lo percibido por static_cast. No es posible satisfacer ese requisito sin tener ambos tipos completamente definidos por el punto static_cast. Entonces, si la (s) definición (es) es (son) no visibles en el punto de static_cast, el código simplemente no se compilará.

Para ilustrar lo anterior con ejemplos: static_cast se puede utilizar [redundantemente] para llevar a cabo descripciones del puntero del objeto. El código

Derived *derived = /* whatever */; 
Base *base = static_cast<Base *>(derived); 

sólo es compilables cuando el código siguiente es compilables

Base *base(derived); 

y por esta compilar la definición de los dos tipos tienen que ser visibles. También se puede usar static_cast para realizar downcasts de punteros de objeto. El código

Base *base = /* whatever */; 
Derived *derived = static_cast<Derived *>(base); 

sólo es compilables cuando el código siguiente es compilables

Base *base(derived); // reverse direction 

y, de nuevo, para este compilar la definición de los dos tipos tienen que ser visibles.

Por lo tanto, simplemente no podrá usar static_cast con tipos indefinidos. Si tu compilador lo permite, es un error en tu compilador.

static_cast puede ser inseguro para los punteros/referencias por un motivo completamente diferente. static_cast puede realizar versiones descendentes jerárquicas para tipos de puntero/referencia de objeto sin verificar el tipo dinámico real del objeto. static_cast también puede realizar actualizaciones jerárquicas para tipos de punteros de método. Usar los resultados de estos lanzamientos no controlados puede llevar a un comportamiento indefinido, si se realiza sin precaución.

En segundo lugar, cuando se usa static_cast con tipos aritméticos, la semántica es totalmente diferente y no tiene nada que ver con lo anterior. Simplemente realiza conversiones de tipo aritmético. Siempre son perfectamente seguros (aparte de los problemas de alcance), siempre que se ajusten a su intención. De hecho, podría ser un buen estilo de programación evitar las conversiones aritméticas para el static_cast y usar moldes antiguos de estilo C, solo para proporcionar una clara diferenciación en el código fuente entre moldes aritméticos siempre seguros y moldes de referencia/puntero jerárquicos potencialmente inseguros .

+0

"usa viejos moldes de estilo C" - eso es lo que suelo hacer también, pero luego alguien se queja y terminamos discutiendo si hay alguna diferencia entre un elenco de estilo C que dicen es malo, y un constructor de argumento único que ellos ' re perfectamente satisfecho con ;-) –

+0

entonces, si static_cast no se compila para punteros/referencias indefinidos, se puede considerar seguro para ese tipo de conversiones? – dimba

+0

@idimba: Er ... Tengo dificultades para tratar de aplicar las nociones de "seguro" e "inseguro" al código que no compila. Incluso diría que tiene poco sentido. – AnT