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

2753

¿Cuáles son los usos adecuados de:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • Yeso estilo C (type)value
  • Reparto de estilo funcional type(value)

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

5
  • 32
  • 4
    Para ver algunos ejemplos concretos útiles del uso de diferentes tipos de moldes, puede verificar la primera respuesta a una pregunta similar en este otro tema . TeaMonkie 24 feb 2017 a las 10:41
  • 3
    Puede encontrar respuestas realmente buenas para su pregunta anterior. Pero me gustaría poner un punto más aquí, @ e.James "No hay nada que estos nuevos operadores de conversión de c ++ puedan hacer y que la conversión de estilo c no pueda. Estos se agregan más o menos para una mejor legibilidad del código". BreakBadSP 9 oct 2018 a las 7:51
  • @BreakBadSP Las nuevas versiones no solo sirven para mejorar la legibilidad del código. Están ahí para hacer que sea más difícil hacer cosas peligrosas, como deshacerse de la constante o lanzar punteros en lugar de sus valores. ¡static_cast tiene muchas menos posibilidades de hacer algo peligroso que un elenco de estilo ac! FourtyTwo 13/01/20 a las 12:50
  • @FourtyTwo estuvo de acuerdoBreakBadSP 16 de enero de 2020 a las 8:36
2777

static_castes el primer yeso que debe intentar usar. Hace cosas como conversiones implícitas entre tipos (como inta float, o puntero a void*), y también puede llamar a funciones de conversión explícitas (o implícitas). En muchos casos, static_castno es necesario indicar explícitamente , pero es importante tener en cuenta que la T(something)sintaxis es equivalente (T)somethingy debe evitarse (más sobre esto más adelante). T(something, something_else)Sin embargo, A es seguro y se garantiza 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 que no se transmita por virtualherencia. Sin embargo, no realiza verificación y es un comportamiento indefinido static_castdescender una jerarquía a un tipo que en realidad no es el tipo del objeto.


const_castse puede usar para eliminar o agregar consta una variable; ningún otro elenco de C ++ es capaz de eliminarlo (ni siquiera reinterpret_cast). Es importante notar que la modificación de un constvalor anterior solo está indefinida si la variable original lo es const; si lo usa para quitar constuna referencia a algo con lo que no se declaró const, es seguro. Esto puede ser útil cuando se sobrecargan funciones miembro basadas const, por ejemplo, en. También se puede usar para agregar consta un objeto, como para llamar a una función miembro sobrecarga.

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


dynamic_castse utiliza exclusivamente para el manejo de polimorfismos. Puede lanzar 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). Puede usarlo para algo más que lanzar hacia abajo: puede lanzar hacia los lados o incluso hacia arriba en otra cadena. El dynamic_castbuscará el 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á utilizando la virtualherencia. También solo puede pasar por herencia pública; siempre fallará en el traspaso protectedo la privateherencia. Sin embargo, esto rara vez es un problema, ya que estas formas de herencia son raras.


reinterpret_castes el yeso más peligroso y debe usarse con mucha moderación. Convierte un tipo directamente en otro, como pasar el valor de un puntero a otro, o almacenar un puntero en un int, o todo tipo de cosas desagradables. En gran medida, la única garantía que tiene reinterpret_castes que normalmente si devuelve 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. Se usa principalmente 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.


La conversión de estilo C y la conversión de estilo de función son conversiones que usan (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), entonces const_cast
  • reinterpret_cast
  • reinterpret_cast, luego const_cast

Por lo tanto, se puede usar como reemplazo de otros lanzamientos en algunos casos, pero puede ser extremadamente peligroso debido a la capacidad de convertirse en a reinterpret_cast, y este último debería ser preferido cuando se necesita un lanzamiento explícito, a menos que esté seguro de que static_casttendrá éxito o reinterpret_castfallará. . Incluso entonces, considere la opción más larga y explícita.

Los lanzamientos de estilo C también ignoran el control de acceso cuando realizan un static_cast, lo que significa que tienen la capacidad de realizar una operación que ningún otro lanzamiento puede realizar. Sin embargo, esto es principalmente una torpeza y, en mi opinión, es solo otra razón para evitar los lanzamientos de estilo C.

19
  • 20
    dynamic_cast es solo para tipos polimórficos. solo necesita usarlo cuando está transmitiendo a una clase derivada. static_cast es ciertamente la primera opción a menos que necesite específicamente la funcionalidad de dynamic_cast. No es una milagrosa "bala de plata" en general. jalf 1 de diciembre de 2008 a las 21:20
  • 3
    ¡Gran respuesta! Un comentario rápido: static_cast podría ser necesario para elevar la jerarquía en caso de que tenga un Derived * & para convertir en Base * &, ya que los dobles punteros / referencias no elevan automáticamente la jerarquía. Me encontré con esa situación (francamente, no común) hace dos minutos. ;-)bartgol 8 abr 13 a las 15:16
  • 5
    * "ningún otro elenco de C ++ es capaz de eliminar const(ni siquiera reinterpret_cast)" ... ¿de verdad? ¿Qué hay de reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))? user541686 15/01/15 a las 11:33
  • 41
    Creo que un detalle importante que falta arriba es que dynamic_cast tiene una penalización de rendimiento en tiempo de ejecución en comparación con static o reinterpret_cast. Esto es importante, por ejemplo, en software en tiempo real. jfritz42 30 de julio de 2015 a las 0:55
  • 9
    Vale la pena mencionar que a reinterpret_castmenudo es el arma de elección cuando se trata de un conjunto de tipos de datos opacos de una APIClass Skeleton 4 de agosto de 2015 a las 13:09
371
  • Úselo dynamic_castpara convertir punteros / referencias dentro de una jerarquía de herencia.

  • Úselo static_castpara conversiones de tipo ordinario.

  • Se utiliza reinterpret_castpara reinterpretar patrones de bits de bajo nivel. Úselo con extrema precaución.

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

1
  • 7
    Tenga cuidado con dynamic_cast. Se basa en RTTI y esto no funcionará como se esperaba en los límites de las bibliotecas compartidas. Simplemente porque crea una biblioteca ejecutable y compartida de forma independiente, no existe una forma estandarizada de sincronizar RTTI en diferentes compilaciones. Por esta razón, en la biblioteca Qt existe qobject_cast <> que usa la información de tipo QObject para verificar tipos. user3150128 23 oct 2018 a las 8:25
221

(Se ha dado mucha explicación teórica y conceptual arriba)

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

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

static_cast:

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);
}
8
  • 37
    La teoría de algunas de las otras respuestas es buena, pero aún confusa, ver estos ejemplos después de leer las otras respuestas realmente hace que todas tengan sentido. Eso es sin los ejemplos, todavía no estaba seguro, pero con ellos, ahora estoy seguro de lo que significan las otras respuestas. Solx 29/04/2014 a las 14:41
  • 1
    Acerca del último uso de reinterpret_cast: ¿no es lo mismo que usar static_cast<char*>(&val)? Lorenzo Belli 27 de mayo de 2016 a las 11:55
  • 5
    @LorenzoBelli Por supuesto que no. ¿Lo intentaste? Este último no es válido en C ++ y bloquea la compilación. static_castsolo funciona entre tipos con conversiones definidas, relación visible por herencia o hacia / desde void *. Para todo lo demás, hay otros moldes. reinterpret casta cualquier char *tipo está permitido para permitir la lectura de la representación de cualquier objeto, y es uno de los únicos casos en los que esa palabra clave es útil, no un generador desenfrenado de implementación / comportamiento indefinido. Pero esto no se considera una conversión "normal", por lo que los (generalmente) muy conservadores no lo permiten static_cast. underscore_d 16/07/2016 a las 22:53
  • 2
    reinterpret_cast es bastante común cuando se trabaja con software del sistema, como bases de datos. En la mayoría de los casos, escribe su propio administrador de páginas que no tiene idea de cuál es el tipo de datos almacenados en la página y solo devuelve un puntero vacío. Depende de los niveles superiores hacer un reparto de reinterpretación e inferirlo como lo que quieran. Sohaib 17 de mayo de 2017 a las 11:46
  • 1
    El primer ejemplo es peligroso, ya que supone un buen comportamiento por parte de la persona que llama (pasar siempre un puntero a un EventDataobjeto real y nada más). Desafortunadamente, no creo que haya ninguna forma práctica de verificar el tipo de un puntero vacío de una manera significativa. Idealmente, el argumento estaría fuertemente tipado. Solo algunas observaciones; no una crítica de la respuesta. Brian A. Henning 29/01/19 a las 15:29
120

Podría ayudar si conoce un poco de los aspectos internos ...

static_cast

  • Compilador de C ++ ya sabe cómo convertir entre tipos de escalado tales como floata int. Úselo static_castpara ellos.
  • Cuando le pide al compilador que convierta de tipo Aa B, static_castllama Bal constructor pasando Acomo param. 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 de tiempo de compilación.
  • La conversión de A*a B*siempre tiene éxito si A y B están en la jerarquía de herencia (o son nulas); de lo contrario, se obtiene un error de compilación.
  • Entendido : si lanza un puntero base a un puntero derivado, pero si el objeto real no es realmente un tipo derivado, entonces no obtiene el error. Obtiene un puntero incorrecto y muy probablemente un error de segmento en tiempo de ejecución. Lo mismo ocurre A&con B&.
  • Gotcha : ¡Cast from Derived to 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 usa la información del tipo de tiempo de ejecución para determinar si la transmisión es válida. Por ejemplo, (Base*)a (Derived*)puede 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 el cast no es válido, dynamic_cast devolverá nullptr.
  • Para A&que B&si elenco no es válido entonces 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 hacer non-const para const, no puede ir 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 como el set<T>que solo devuelve sus elementos como const para asegurarse de que no cambie su clave. Sin embargo, si su intención es modificar los miembros no clave del objeto, debería estar bien. Puede usar const_cast para eliminar la constness.
  • Otro ejemplo es cuando desea implementar T& SomeClass::foo()así como 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.

reinterpret_cast

  • Esto básicamente dice que tome estos bytes en esta ubicación de memoria y piense en ello como un objeto dado.
  • Por ejemplo, puede cargar 4 bytes de floata 4 bytes de intpara ver cómo se ven los bits en float.
  • Obviamente, si los datos no son correctos para el tipo, puede obtener segfault.
  • No hay sobrecarga de tiempo de ejecución para esta transmisión.
2
  • Agregué la información del operador de conversión, pero hay algunas otras cosas que también deberían arreglarse y no me siento cómodo actualizando esto demasiado. Los elementos son: 1. If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.Obtienes UB que puede resultar en una falla de segmento en tiempo de ejecución si tienes suerte. 2. Los modelos dinámicos también se pueden utilizar en fundición cruzada. 3. Los yesos constantes pueden resultar en UB en algunos casos. Usar mutablepuede ser una mejor opción para implementar la coherencia lógica. Adrian 12/11/18 a las 20:33
  • 1
    @Adrian, tienes razón en todos los aspectos. La respuesta está escrito para gente de más o menos el nivel de principiante y yo no quería abrumarlos con todas las otras complicaciones que vienen con mutable, fundición, etc. cruzShital Shah 16/11/18 a las 11:20
17

¿ Responde esto a tu pregunta?

Nunca lo he usado reinterpret_cast, y me pregunto si encontrarme con un estuche que lo necesita no es un olor a mal diseño. En el código base en el que trabajo dynamic_castse usa mucho. La diferencia con static_castes que realiza una dynamic_castverificación en tiempo de ejecución que puede ser (más seguro) o no (más sobrecarga) lo que desea (consulte msdn ).

6
  • 3
    He usado reintrepret_cast para un propósito: obtener los bits de un doble (del mismo tamaño que en mi plataforma). Joshua 14 de nov. De 2009 a las 22:33
  • 2
    reinterpret_cast es necesario, por ejemplo, para trabajar con objetos COM. CoCreateInstance () tiene un parámetro de salida de tipo void ** (el último parámetro), en el que pasará su puntero declarado como, por ejemplo, "INetFwPolicy2 * pNetFwPolicy2". Para hacer eso, necesita escribir algo como reinterpret_cast <void **> (& pNetFwPolicy2). Serge Rogatch 31 de mayo de 2015 a las 13:44
  • 1
    Quizás haya un enfoque diferente, pero yo uso reinterpret_castpara extraer datos de una matriz. Por ejemplo, si tengo un char*búfer que contiene un gran búfer lleno de datos binarios empaquetados, necesito moverme y obtener primitivas individuales de diferentes tipos. Algo como esto:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }James Matta 23/08/18 a las 19:34
  • Nunca lo he usado reinterpret_cast, no tiene muchos usos. Pika the Master of the Whales 13 oct 2018 a las 16:31
  • Personalmente, solo lo he visto reinterpret_castusado por una razón. He visto datos de objetos sin procesar almacenados en un tipo de datos "blob" en una base de datos, luego, cuando los datos se recuperan de la base de datos, reinterpret_castse utilizan para convertir estos datos sin procesar en el objeto. ImaginaryHuman072889 7/10/19 a las 11:29
dieciséis

Además de las otras respuestas hasta ahora, aquí hay un ejemplo no obvio en el static_castque no es suficiente para que reinterpret_castsea ​​necesario. Supongamos que hay una función que en un parámetro de salida devuelve punteros a objetos de diferentes clases (que no comparten una clase base común). Un ejemplo real de tal función es CoCreateInstance()(ver el último parámetro, que de hecho es void**). Suponga que solicita una clase particular de objeto de esta función, de modo que sepa de antemano el tipo del puntero (lo que suele hacer para los objetos COM). En este caso, no puede convertir el puntero a su puntero en void**con static_cast: necesita reinterpret_cast<void**>(&yourPointer).

En codigo:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    //static_cast<void**>(&pNetFwPolicy2) would give a compile error
    reinterpret_cast<void**>(&pNetFwPolicy2) );

Sin embargo, static_castfunciona para punteros simples (no punteros a punteros), por lo que el código anterior se puede reescribir para evitar reinterpret_cast(al precio de una variable adicional) de la siguiente manera:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    &tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
1
  • 1
    ¿No funcionaría algo como en &static_cast<void*>(pNetFwPolicy2)lugar de static_cast<void**>(&pNetFwPolicy2)? jp48 14/08/19 a las 17:26
12

Si bien otras respuestas describieron muy bien todas las diferencias entre las conversiones de C ++, me gustaría agregar una breve nota de por qué no debería usar conversiones de estilo C (Type) vary Type(var).

Para los principiantes de C ++, las conversiones de estilo C parecen ser la operación de superconjunto sobre las conversiones de C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) y alguien podría preferirlas a las conversiones de C ++ . De hecho, el elenco de estilo C es el superconjunto y es más corto de escribir.

El principal problema de los elencos estilo C es que ocultan la intención real del desarrollador del elenco. Los lanzamientos de estilo C pueden hacer prácticamente todos los tipos de lanzamiento, desde lanzamientos normalmente seguros realizados por static_cast <> () y dynamic_cast <> () hasta lanzamientos potencialmente peligrosos como const_cast <> (), donde el modificador const se puede eliminar para que las variables const se puede modificar y reinterpretar_cast <> () que incluso puede reinterpretar valores enteros en punteros.

Aquí está la muestra.

int a=rand(); // Random number.

int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.

int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.

int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.

*pa4=5; // Program crashes.

La razón principal por la que se agregaron conversiones de C ++ al lenguaje fue para permitir que un desarrollador aclare sus intenciones: por qué va a hacer esa transmisión. Al usar conversiones de estilo C que son perfectamente válidas en C ++, está haciendo que su código sea menos legible y más propenso a errores, especialmente para otros desarrolladores que no crearon su código. Por lo tanto, para que su código sea más legible y explícito, siempre debe preferir las conversiones de C ++ sobre las conversiones de estilo C.

Aquí hay una breve cita del libro de Bjarne Stroustrup (el autor de C ++) The C ++ Programming Language 4th edition - página 302.

This C-style cast is far more dangerous than the named conversion operators because the notation is harder to spot in a large program and the kind of conversion intended by the programmer is not explicit.

1
  • Votación a favor debido a la referencia a la cotización de Stroustrup. Es difícil de encontrar en estos días, especialmente porque a menudo lo escuchamos de personas muy inteligentes en lugar del hombre mismo. eigenfield 14/08/20 a las 4:02
10

static_castvs dynamic_castvs reinterpret_castvista interna en un downcast / upcast

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

Creo que esto dará una buena intuición sobre cómo esos moldes son diferentes:

  • static_cast: realiza un desplazamiento de dirección en tiempo de ejecución (impacto de tiempo de ejecución bajo) y no comprueba de seguridad que un abatimiento sea correcto.

  • dyanamic_cast: hace el mismo desplazamiento de dirección en tiempo de ejecución como static_cast, pero también y una costosa verificación de seguridad de que un downcast es correcto 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 nullptrque indica un downcast no válido.

    Por lo tanto, si su código no puede verificar eso nullptry tomar una acción válida de no aborto, debe usar en static_castlugar de la transmisión dinámica.

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

  • reinterpret_cast: no hace nada en tiempo de ejecución, ni siquiera el desplazamiento de la 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 estén involucradas secuencias de bytes sin procesar.

Considere el siguiente ejemplo de código:

main.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

where setarchse utiliza para deshabilitar ASLR y facilitar la comparación de ejecuciones.

Salida posible:

&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 al método virtual de manera eficiente, suponiendo que las estructuras de datos de memoria de B1 tengan 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 de Dtiene que parecerse a algo como:

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 Dcontiene en su interior una estructura de memoria idéntica a la de B1y B2, es decir:

  • +0 se ve exactamente como un B1, con la tabla v B1 para D seguida de int_in_b1
  • +8 se ve exactamente como un B2, con el B2 vtable para D seguido de int_in_b2

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

an upcast or downcast only needs to shift the pointer value by a value known at compile time

De esta manera, cuando Dse pasa a la matriz de tipo base, el tipo de conversión en realidad calcula ese desplazamiento y apunta algo que se ve exactamente como un válido B2en la memoria, excepto que este tiene el vtable para en Dlugar de B2, y 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 alcanzar la correspondiente estructura de datos similar a B2.

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

Desde la salida stdout vemos:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

Por lo tanto, lo implícito static_casthecho 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 0x7fffffffc930 y 0x7fffffffc940 probablemente sean los B1datos y vtable.

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

  • static_cast<D*>(b2s[0]) 0x7fffffffc910: el compilador subió 0x10 en los bytes de tiempo de compilación para intentar ir de B2a al contenedorD

    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 si es así, devuelva NULL
    • de lo contrario, reste 0x10 para llegar al Dque no existe
  • dynamic_cast<D*>(b2s[0]) 0: ¡C ++ realmente descubrió que el elenco no era válido y regresó nullptr!

    No hay forma de que esto se pueda hacer en tiempo de compilación, y lo confirmaremos a partir del 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 <[email protected]>
       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, establece 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 en memoria RTTI adicionales que representan la jerarquía de clases.

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

    ¡Es por eso que el reparto dinámico es potencialmente caro! Aquí hay un ejemplo en el que un parche de una sola línea que convierte dynamic_casta static_casten a en 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 hace cálculos de compensación.

    Pero esto es incorrecto, 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:

Probado en Ubuntu 18.04 amd64, GCC 7.4.0.

2
5

Para entenderlo, consideremos el siguiente fragmento de código:

struct Foo{};
struct Bar{};

int main(int argc, char** argv)
{
    Foo* f = new Foo;

    Bar* b1 = f;                              // (1)
    Bar* b2 = static_cast<Bar*>(f);           // (2)
    Bar* b3 = dynamic_cast<Bar*>(f);          // (3)
    Bar* b4 = reinterpret_cast<Bar*>(f);      // (4)
    Bar* b5 = const_cast<Bar*>(f);            // (5)

    return 0;
}

Solo la línea (4) se compila sin errores. Solo reinterpret_cast se puede utilizar para convertir un puntero en un objeto en un puntero en cualquier tipo de objeto no relacionado.

Uno de estos a tener en cuenta es: el dynamic_cast fallaría en tiempo de ejecución, sin embargo, en la mayoría de los compiladores tampoco se compilará porque no hay funciones virtuales en la estructura del puntero que se está lanzando , lo que significa que dynamic_cast funcionará solo con punteros de clase polimórfica .

Cuándo usar la conversión de C ++ :

  • Use static_cast como el equivalente de un elenco de estilo C que sí valora la conversión, o cuando necesitemos elevar explícitamente un puntero de una clase a su superclase.
  • Utilice const_cast para eliminar el calificador const.
  • Utilice reinterpret_cast para realizar conversiones inseguras de tipos de punteros hacia y desde enteros y otros tipos de punteros. Use esto solo si sabemos lo que estamos haciendo y comprendemos los problemas de alias.
1
  • El fragmento proporcionado es un mal ejemplo. Si bien estoy de acuerdo en que, de hecho, se compila. La lista de When es vagamente correcta, pero en su mayoría está llena de opiniones insuficientes para comprender la granularidad requerida. eigenfield 14 de agosto de 2020 a las 4:13