¿Cuándo se deben utilizar static_cast,dynamic_cast, const_cast y reinterpret_cast?

Resuelto e.James asked hace 16 años • 12 respuestas

¿Cuáles son los usos adecuados de:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • (type)value(elenco estilo C)
  • type(value)(reparto de estilo funcional)

¿Cómo se decide cuál utilizar en qué casos específicos?

e.James avatar Dec 02 '08 03:12 e.James
Aceptado

static_cast

static_castes el primer yeso que debes intentar utilizar. Hace cosas como conversiones implícitas entre tipos (como inta floato puntero a void*) y también puede llamar a funciones de conversión explícitas (o implícitas). En muchos casos, no es necesario indicarlo explícitamente static_cast, pero es importante tener en cuenta que la T(something)sintaxis es equivalente a (T)somethingy debe evitarse (más sobre esto más adelante). T(something, something_else)Sin embargo, A es seguro y está garantizado que llamará al constructor.

static_castTambién se puede transmitir a través de jerarquías de herencia. No es necesario cuando se lanza hacia arriba (hacia una clase base), pero cuando se lanza hacia abajo se puede usar siempre y cuando no se transmita por virtualherencia. Sin embargo, no realiza comprobaciones y es un comportamiento indefinido bajar static_castuna jerarquía a un tipo que en realidad no es el tipo del objeto.

const_cast

const_castse puede utilizar para eliminar o agregar constuna variable; ninguna otra versión de C++ es capaz de eliminarlo (ni siquiera reinterpret_cast). Es importante tener en cuenta que modificar un constvalor anterior solo es indefinido si la variable original es const; si lo usa para eliminar constuna referencia a algo que no se declaró con const, es seguro. Esto puede resultar útil al sobrecargar funciones miembro basadas en const, por ejemplo. También se puede usar para agregar consta un objeto, como para llamar a una sobrecarga de función miembro.

const_casttambién funciona de manera similar volatile, aunque es menos común.

dynamic_cast

dynamic_castse utiliza exclusivamente para manejar polimorfismo. Puede convertir un puntero o referencia a cualquier tipo polimórfico a cualquier otro tipo de clase (un tipo polimórfico tiene al menos una función virtual, declarada o heredada). Puedes usarlo para algo más que lanzar hacia abajo: puedes lanzar hacia un lado o incluso hacia arriba de otra cadena. Buscará dynamic_castel objeto deseado y lo devolverá si es posible. Si no puede, regresará nullptren el caso de un puntero o arrojará std::bad_casten el caso de una referencia.

dynamic_castSin embargo, tiene algunas limitaciones. No funciona si hay varios objetos del mismo tipo en la jerarquía de herencia (el llamado "diamante temido") y no estás usando virtualla herencia. Además, sólo puede pasar por herencia pública; siempre no pasará protectedpor privateherencia. Sin embargo, esto rara vez es un problema, ya que este tipo de formas de herencia son raras.

reinterpret_cast

reinterpret_castEs el yeso más peligroso y debe usarse con mucha moderación. Convierte un tipo directamente en otro, como convertir el valor de un puntero a otro, o almacenar un puntero en un archivo int, o todo tipo de otras cosas desagradables. En gran medida, la única garantía que obtiene reinterpret_castes que normalmente, si vuelve a convertir el resultado al tipo original, obtendrá exactamente el mismo valor (pero no si el tipo intermedio es más pequeño que el tipo original). Hay una serie de conversiones que reinterpret_casttampoco se pueden realizar. A menudo se abusa de él para conversiones y manipulaciones de bits particularmente extrañas, como convertir un flujo de datos sin procesar en datos reales o almacenar datos en los bits bajos de un puntero a datos alineados. Para esos casos, ver std::bit_cast.

Elenco estilo C y elenco estilo función

La conversión estilo C y la conversión estilo función son conversiones que utilizan (type)objecto type(object), respectivamente, y son funcionalmente equivalentes. Se definen como el primero de los siguientes que tiene éxito:

  • const_cast
  • static_cast(aunque ignorando las restricciones de acceso)
  • static_cast(ver arriba), entoncesconst_cast
  • reinterpret_cast
  • reinterpret_cast, entoncesconst_cast

Por lo tanto, puede usarse como reemplazo de otras conversiones en algunos casos, pero puede ser extremadamente peligroso debido a la capacidad de transformarse en un reinterpret_cast, y se debe preferir este último cuando se necesita una conversión explícita, a menos que esté seguro de static_castque tendrá éxito o reinterpret_castfracasará. . Incluso entonces, considere la opción más larga y explícita.

Las conversiones estilo C también ignoran el control de acceso cuando realizan una operación static_cast, lo que significa que tienen la capacidad de realizar una operación que ninguna otra conversión puede realizar. Sin embargo, esto es principalmente una pifia y, en mi opinión, es solo otra razón para evitar los elencos de estilo C.

std::bit_cast[C++20]

std::bit_castcopia los bits y bytes del objeto de origen (su representación) directamente en un nuevo objeto del tipo de destino. Es una forma compatible con los estándares de hacer juegos de palabras tipográficos. Si te encuentras escribiendo *reinterpret_cast<SomeType*>(&x), probablemente deberías utilizarlo std::bit_cast<SomeType>(x)en su lugar.

std::bit_castse declara en <bit>. Los objetos deben tener el mismo tamaño y ser fácilmente copiables. Si aún no puede usar C++ 20, úselo memcpypara copiar el valor fuente en una variable del tipo deseado.

coppro avatar Dec 01 '2008 20:12 coppro
  • Úselo dynamic_castpara convertir punteros/referencias dentro de una jerarquía de herencia.

  • Úselo static_castpara conversiones de tipos normales.

  • Úselo reinterpret_castpara la reinterpretación de bajo nivel de patrones de bits. Úselo con extrema precaución.

  • Úselo const_castpara desechar const/volatile. Evite esto a menos que esté atrapado usando una API constante incorrecta.

Fred Larson avatar Dec 01 '2008 20:12 Fred Larson

(Arriba se han dado muchas explicaciones teóricas y conceptuales)

A continuación se muestran algunos de los ejemplos prácticos cuando utilicé static_cast , dynamic_cast , const_cast , reinterpret_cast .

(También consulte esto para comprender la explicación: http://www.cplusplus.com/doc/tutorial/typecasting/ )

transmisión_estática:

OnEventData(void* pData)

{
  ......

  //  pData is a void* pData, 

  //  EventData is a structure e.g. 
  //  typedef struct _EventData {
  //  std::string id;
  //  std:: string remote_id;
  //  } EventData;

  // On Some Situation a void pointer *pData
  // has been static_casted as 
  // EventData* pointer 

  EventData *evtdata = static_cast<EventData*>(pData);
  .....
}

transmisión_dinámica:

void DebugLog::OnMessage(Message *msg)
{
    static DebugMsgData *debug;
    static XYZMsgData *xyz;

    if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
        // debug message
    }
    else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
        // xyz message
    }
    else/* if( ... )*/{
        // ...
    }
}

const_cast:

// *Passwd declared as a const

const unsigned char *Passwd


// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

reinterpret_cast:

typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read. 

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
Sumit Arora avatar Jan 21 '2014 04:01 Sumit Arora

Podría ser útil si conoces un poco los aspectos internos...

transmisión_estática

  • El compilador de C++ ya sabe cómo convertir entre tipos de escalador floatcomo int. Úselo static_castpara ellos.
  • Cuando le pide al compilador que convierta de tipo Aa B, static_castllama Bal constructor pasándolo Acomo parámetro. Alternativamente, Apodría tener un operador de conversión (es decir A::operator B()). Si Bno tiene dicho constructor, o Ano tiene un operador de conversión, obtendrá un error en tiempo de compilación.
  • La transmisión desde A*a B*siempre se realiza correctamente si A y B están en la jerarquía de herencia (o son nulos); de lo contrario, obtendrá un error de compilación.
  • Entendido : si convierte el puntero base en un puntero derivado pero si el objeto real no es realmente un tipo derivado, entonces no obtendrá ningún error. Obtiene un puntero incorrecto y muy probablemente un error de segmentación en tiempo de ejecución. Lo mismo ocurre A&con B&.
  • Gotcha : ¡Transmitir desde Derivado a Base o viceversa crea una nueva copia! Para las personas que vienen de C#/Java, esto puede ser una gran sorpresa porque el resultado es básicamente un objeto cortado creado a partir de Derived.

transmisión_dinámica

  • Dynamic_cast utiliza información del tipo de tiempo de ejecución para determinar si la conversión es válida. Por ejemplo, (Base*)puede (Derived*)fallar si el puntero no es realmente de tipo derivado.
  • Esto significa que Dynamic_cast es muy caro en comparación con static_cast.
  • Para A*to B*, si la conversión no es válida, dinámica_cast devolverá nullptr.
  • Porque si la conversión no es válida A&, B&Dynamic_cast arrojará una excepción bad_cast.
  • A diferencia de otras conversiones, hay una sobrecarga de tiempo de ejecución.

const_cast

  • Si bien static_cast puede funcionar de manera no constante a constante, no puede ser al revés. El const_cast puede funcionar en ambos sentidos.
  • Un ejemplo en el que esto resulta útil es iterar a través de algún contenedor set<T>que solo devuelve sus elementos como constantes para asegurarse de no cambiar su clave. Sin embargo, si su intención es modificar los miembros que no son clave del objeto, entonces debería estar bien. Puedes usar const_cast para eliminar la constancia.
  • Otro ejemplo es cuando desea implementar T& SomeClass::foo()además de const T& SomeClass::foo() const. Para evitar la duplicación de código, puede aplicar const_cast para devolver el valor de una función de otra.

reinterpretar_cast

  • Básicamente, esto dice que tome estos bytes en esta ubicación de memoria y considérelo como un objeto determinado.
  • Por ejemplo, puede cargar de 4 bytes floata 4 bytes de intpara ver cómo floatse ven los bits.
  • Obviamente, si los datos no son correctos para el tipo, es posible que se produzca un error de segmentación.
  • No hay sobrecarga de tiempo de ejecución para esta transmisión.
Shital Shah avatar Dec 11 '2016 02:12 Shital Shah

static_castvs dynamic_castvs reinterpret_castvista interna en un abatido/upcast

En esta respuesta, quiero comparar estos tres mecanismos en un ejemplo concreto de upcast/downcast y analizar qué sucede con los punteros/memoria/ensamblado subyacentes para brindar una comprensión concreta de cómo se comparan.

Creo que esto dará una buena intuición sobre en qué se diferencian esos elencos:

  • static_cast: realiza una compensación de dirección en tiempo de ejecución (bajo impacto en tiempo de ejecución) y no realiza comprobaciones de seguridad de que una conversión abatida sea correcta.

  • dyanamic_cast: realiza la misma compensación de dirección en tiempo de ejecución como static_cast, pero también realiza una costosa verificación de seguridad de que una conversión abatida es correcta usando RTTI.

    Esta verificación de seguridad le permite consultar si un puntero de clase base es de un tipo determinado en tiempo de ejecución al verificar un retorno que nullptrindica una conversión no válida.

    Por lo tanto, si su código no puede verificar eso nullptry realizar una acción válida que no sea de cancelación, simplemente debe usar static_casten lugar de la conversión dinámica.

    Si cancelar es la única acción que su código puede realizar, tal vez solo desee habilitar las dynamic_castcompilaciones de depuración ( -NDEBUG) y usarlas static_castde otra manera, por ejemplo, como se hace aquí , para no ralentizar sus ejecuciones rápidas.

  • reinterpret_cast: no hace nada en tiempo de ejecución, ni siquiera el desplazamiento de dirección. El puntero debe apuntar exactamente al tipo correcto, ni siquiera una clase base funciona. Por lo general, no desea esto a menos que se trate de flujos de bytes sin formato.

Considere el siguiente ejemplo de código:

principal.cpp

#include <iostream>

struct B1 {
    B1(int int_in_b1) : int_in_b1(int_in_b1) {}
    virtual ~B1() {}
    void f0() {}
    virtual int f1() { return 1; }
    int int_in_b1;
};

struct B2 {
    B2(int int_in_b2) : int_in_b2(int_in_b2) {}
    virtual ~B2() {}
    virtual int f2() { return 2; }
    int int_in_b2;
};

struct D : public B1, public B2 {
    D(int int_in_b1, int int_in_b2, int int_in_d)
        : B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
    void d() {}
    int f2() { return 3; }
    int int_in_d;
};

int main() {
    B2 *b2s[2];
    B2 b2{11};
    D *dp;
    D d{1, 2, 3};

    // The memory layout must support the virtual method call use case.
    b2s[0] = &b2;
    // An upcast is an implicit static_cast<>().
    b2s[1] = &d;
    std::cout << "&d           " << &d           << std::endl;
    std::cout << "b2s[0]       " << b2s[0]       << std::endl;
    std::cout << "b2s[1]       " << b2s[1]       << std::endl;
    std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
    std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;

    // Now for some downcasts.

    // Cannot be done implicitly
    // error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
    // dp = (b2s[0]);

    // Undefined behaviour to an unrelated memory address because this is a B2, not D.
    dp = static_cast<D*>(b2s[0]);
    std::cout << "static_cast<D*>(b2s[0])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[0])->int_in_d  " << dp->int_in_d << std::endl;

    // OK
    dp = static_cast<D*>(b2s[1]);
    std::cout << "static_cast<D*>(b2s[1])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[1])->int_in_d  " << dp->int_in_d << std::endl;

    // Segfault because dp is nullptr.
    dp = dynamic_cast<D*>(b2s[0]);
    std::cout << "dynamic_cast<D*>(b2s[0])           " << dp           << std::endl;
    //std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;

    // OK
    dp = dynamic_cast<D*>(b2s[1]);
    std::cout << "dynamic_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;

    // Undefined behaviour to an unrelated memory address because this
    // did not calculate the offset to get from B2* to D*.
    dp = reinterpret_cast<D*>(b2s[1]);
    std::cout << "reinterpret_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}

Compile, ejecute y desmonte con:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out

donde setarchse utiliza para desactivar ASLR y facilitar la comparación de ejecuciones.

Posible salida:

&d           0x7fffffffc930
b2s[0]       0x7fffffffc920
b2s[1]       0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0])            0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d  1
static_cast<D*>(b2s[1])            0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d  3
dynamic_cast<D*>(b2s[0])           0
dynamic_cast<D*>(b2s[1])           0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1])           0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767

Ahora, como se menciona en: https://en.wikipedia.org/wiki/Virtual_method_table para soportar las llamadas a métodos virtuales de manera eficiente, suponiendo que las estructuras de datos de memoria de B1 tienen la forma:

B1:
  +0: pointer to virtual method table of B1
  +4: value of int_in_b1

y B2es de forma:

B2:
  +0: pointer to virtual method table of B2
  +4: value of int_in_b2

entonces la estructura de datos de la memoria Ddebe verse así:

D:
  +0: pointer to virtual method table of D (for B1)
  +4: value of int_in_b1
  +8: pointer to virtual method table of D (for B2)
 +12: value of int_in_b2
 +16: value of int_in_d

El hecho clave es que la estructura de datos de la memoria de Dcontiene en su interior una estructura de memoria idéntica a la de B1y B2, es decir:

  • +0 se ve exactamente como B1, con la tabla virtual B1 para D seguida deint_in_b1
  • +8 se ve exactamente como un B2, con la tabla B2 para D seguida deint_in_b2

o en un nivel superior:

D:
   +0: B1
   +8: B2
  +16: <fields of D itsef>

Por lo tanto llegamos a la conclusión crítica:

un upcast o downcast solo necesita cambiar el valor del puntero por un valor conocido en el momento de la compilación

De esta manera, cuando Dse pasa a la matriz de tipo base, la conversión de tipos realmente calcula ese desplazamiento y señala algo que se ve exactamente como válido B2en la memoria, excepto que este tiene el vtable en Dlugar de B2y, por lo tanto, todas las llamadas virtuales funcionan de forma transparente.

P.ej:

b2s[1] = &d;

simplemente necesita obtener la dirección de d+ 8 para llegar a la estructura de datos similar a B2 correspondiente.

Ahora finalmente podemos volver a la conversión de tipos y al análisis de nuestro ejemplo concreto.

Desde la salida estándar vemos:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

Por lo tanto, lo implícito static_castrealizado allí calculó correctamente el desplazamiento de la Destructura de datos completa en 0x7fffffffc930 a la B2similar que está en 0x7fffffffc940. También inferimos que lo que se encuentra entre 0x7ffffffc930 y 0x7fffffffc940 probablemente sean los B1datos y la tabla virtual.

Luego, en las secciones abatidas, ahora es fácil entender cómo fallan las no válidas y por qué:

  • static_cast<D*>(b2s[0]) 0x7fffffffc910: el compilador simplemente subió 0x10 en bytes en tiempo de compilación para intentar ir de a B2a lo que contieneD

    Pero como b2s[0]no era un D, ahora apunta a una región de memoria indefinida.

    El desmontaje es:

    49          dp = static_cast<D*>(b2s[0]);
       0x0000000000000fc8 <+414>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fcc <+418>:   48 85 c0        test   %rax,%rax
       0x0000000000000fcf <+421>:   74 0a   je     0xfdb <main()+433>
       0x0000000000000fd1 <+423>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fd5 <+427>:   48 83 e8 10     sub    $0x10,%rax
       0x0000000000000fd9 <+431>:   eb 05   jmp    0xfe0 <main()+438>
       0x0000000000000fdb <+433>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000000fe0 <+438>:   48 89 45 98     mov    %rax,-0x68(%rbp)
    

    entonces vemos que GCC hace:

    • compruebe si el puntero es NULL y, en caso afirmativo, devuelva NULL
    • de lo contrario, reste 0x10 para llegar al Dque no existe
  • dynamic_cast<D*>(b2s[0]) 0: ¡C ++ realmente descubrió que la conversión no era válida y la devolvió nullptr!

    No hay forma de que esto se pueda hacer en tiempo de compilación, y lo confirmaremos desde el desmontaje:

    59          dp = dynamic_cast<D*>(b2s[0]);
       0x00000000000010ec <+706>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x00000000000010f0 <+710>:   48 85 c0        test   %rax,%rax
       0x00000000000010f3 <+713>:   74 1d   je     0x1112 <main()+744>
       0x00000000000010f5 <+715>:   b9 10 00 00 00  mov    $0x10,%ecx
       0x00000000000010fa <+720>:   48 8d 15 f7 0b 20 00    lea    0x200bf7(%rip),%rdx        # 0x201cf8 <_ZTI1D>
       0x0000000000001101 <+727>:   48 8d 35 28 0c 20 00    lea    0x200c28(%rip),%rsi        # 0x201d30 <_ZTI2B2>
       0x0000000000001108 <+734>:   48 89 c7        mov    %rax,%rdi
       0x000000000000110b <+737>:   e8 c0 fb ff ff  callq  0xcd0 <__dynamic_cast@plt>
       0x0000000000001110 <+742>:   eb 05   jmp    0x1117 <main()+749>
       0x0000000000001112 <+744>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000001117 <+749>:   48 89 45 98     mov    %rax,-0x68(%rbp)
    

    Primero hay una verificación NULL y devuelve NULL si la entrada es NULL.

    De lo contrario, configura algunos argumentos en RDX, RSI y RDI y llama __dynamic_cast.

    No tengo la paciencia para analizar esto más a fondo ahora, pero como dijeron otros, la única forma de que esto funcione es __dynamic_castacceder a algunas estructuras de datos RTTI en memoria adicionales que representan la jerarquía de clases.

    Por lo tanto, debe comenzar desde la B2entrada de esa tabla y luego recorrer esta jerarquía de clases hasta encontrar que la vtable para un Dencasillado de b2s[0].

    ¡Es por eso que el reparto dinámico es potencialmente costoso! A continuación se muestra un ejemplo en el que un parche de una sola línea que convierte a dynamic_casten a static_casten un proyecto complejo redujo el tiempo de ejecución en un 33 %. .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940éste simplemente nos cree ciegamente: dijimos que hay una Ddirección at b2s[1]y el compilador no realiza cálculos de compensación.

    Pero esto está mal, porque D está en realidad en 0x7fffffffc930, ¡lo que está en 0x7fffffffc940 es la estructura similar a B2 dentro de D! Entonces se accede a la basura.

    Podemos confirmar esto a partir del horrendo -O0ensamblaje que simplemente mueve el valor:

    70          dp = reinterpret_cast<D*>(b2s[1]);
       0x00000000000011fa <+976>:   48 8b 45 d8     mov    -0x28(%rbp),%rax
       0x00000000000011fe <+980>:   48 89 45 98     mov    %rax,-0x68(%rbp)
    

Preguntas relacionadas:

  • ¿Cómo se implementa Dynamic_cast?
  • Downcasting usando 'static_cast' en C++

Probado en Ubuntu 18.04 amd64, GCC 7.4.0.