C++
Conversiones de tipo explícito
Buscar..
Introducción
Una expresión puede ser convertido o fundido para escribir explícitamente T
usando dynamic_cast<T>
, static_cast<T>
, reinterpret_cast<T>
, o const_cast<T>
, dependiendo de qué tipo de molde que se pretende.
C ++ también admite la notación de conversión de estilo de función, T(expr)
y la notación de conversión de estilo de C, (T)expr
.
Sintaxis
- especificador de tipo simple
(
)
- especificador de tipo simple
(
expresión-lista)
- especificador de tipo simple braced-init-list
- typename-specifier
(
)
- typename-specifier
(
expresión-lista)
- nombre de archivo-especificador braced- init-list
-
dynamic_cast
<
type-id>
(
expresión)
-
static_cast
<
type-id>
(
expresión)
-
reinterpret_cast
<
type-id>
(
expresión)
-
const_cast
<
type-id>
(
expresión)
-
(
type-id)
expresión-cast
Observaciones
Las seis notaciones emitidas tienen una cosa en común:
- La
dynamic_cast<Derived&>(base)
a un tipo de referenciadynamic_cast<Derived&>(base)
, como endynamic_cast<Derived&>(base)
, produce un lvalue. Por lo tanto, cuando desea hacer algo con el mismo objeto pero tratarlo como un tipo diferente, se convertiría a un tipo de referencia de valor l. - La conversión a un tipo de referencia de rvalue, como en
static_cast<string&&>(s)
, produce un rvalue. - La conversión a un tipo que no es de referencia, como en
(int)x
, produce un prvalue, que puede considerarse como una copia del valor que se está emitiendo, pero con un tipo diferente del original.
La palabra clave reinterpret_cast
es responsable de realizar dos tipos diferentes de conversiones "inseguras":
- Las conversiones de "tipo punning" , que se pueden usar para acceder a la memoria de un tipo como si fuera de un tipo diferente.
- Conversiones entre tipos enteros y tipos de punteros , en cualquier dirección.
La palabra clave static_cast
puede realizar una variedad de diferentes conversiones:
Cualquier conversión que se pueda realizar mediante una inicialización directa, incluidas las conversiones implícitas y las conversiones que llaman a un constructor explícito o una función de conversión. Vea aquí y aquí para más detalles.
Para
void
, lo que descarta el valor de la expresión.// on some compilers, suppresses warning about x being unused static_cast<void>(x);
Entre los tipos aritméticos y de enumeración, y entre diferentes tipos de enumeración. Ver las conversiones de enumeración
Del puntero al miembro de la clase derivada, al puntero del miembro de la clase base. Los tipos apuntados deben coincidir. Ver conversión derivada a base para punteros a miembros
- Desde un lvalue a un xvalue, como en
std::move
. Ver movimiento semántico .
Base a conversión derivada
Un puntero a la clase base se puede convertir en un puntero a la clase derivada usando static_cast
. static_cast
no realiza ninguna comprobación en tiempo de ejecución y puede provocar un comportamiento indefinido cuando el puntero no apunta al tipo deseado.
struct Base {};
struct Derived : Base {};
Derived d;
Base* p1 = &d;
Derived* p2 = p1; // error; cast required
Derived* p3 = static_cast<Derived*>(p1); // OK; p2 now points to Derived object
Base b;
Base* p4 = &b;
Derived* p5 = static_cast<Derived*>(p4); // undefined behaviour since p4 does not
// point to a Derived object
Del mismo modo, una referencia a la clase base se puede convertir en una referencia a la clase derivada usando static_cast
.
struct Base {};
struct Derived : Base {};
Derived d;
Base& r1 = d;
Derived& r2 = r1; // error; cast required
Derived& r3 = static_cast<Derived&>(r1); // OK; r3 now refers to Derived object
Si el tipo de fuente es polimórfico, dynamic_cast
se puede usar para realizar una conversión de base a derivada. Realiza una verificación en tiempo de ejecución y la falla es recuperable en lugar de producir un comportamiento indefinido. En el caso del puntero, se devuelve un puntero nulo en caso de error. En el caso de referencia, se produce una excepción en caso de error de tipo std::bad_cast
(o una clase derivada de std::bad_cast
).
struct Base { virtual ~Base(); }; // Base is polymorphic
struct Derived : Base {};
Base* b1 = new Derived;
Derived* d1 = dynamic_cast<Derived*>(b1); // OK; d1 points to Derived object
Base* b2 = new Base;
Derived* d2 = dynamic_cast<Derived*>(b2); // d2 is a null pointer
Arrojando constness
Un puntero a un objeto const se puede convertir en un puntero a un objeto no constante utilizando la palabra clave const_cast
. Aquí usamos const_cast
para llamar a una función que no es const-correcta. Solo acepta un argumento no const char*
aunque nunca escribe a través del puntero:
void bad_strlen(char*);
const char* s = "hello, world!";
bad_strlen(s); // compile error
bad_strlen(const_cast<char*>(s)); // OK, but it's better to make bad_strlen accept const char*
const_cast
puede utilizar const_cast
al tipo de referencia para convertir un lvalue cualificado por const en un valor no calificado por const.
const_cast
es peligroso porque hace imposible que el sistema de tipo C ++ impida que intente modificar un objeto const. Si lo hace, se traduce en un comportamiento indefinido.
const int x = 123;
int& mutable_x = const_cast<int&>(x);
mutable_x = 456; // may compile, but produces *undefined behavior*
Tipo de conversión de punning
Un puntero (referencia de referencia) a un tipo de objeto se puede convertir en un puntero (referencia de referencia) a cualquier otro tipo de objeto utilizando reinterpret_cast
. Esto no llama a ningún constructor o funciones de conversión.
int x = 42;
char* p = static_cast<char*>(&x); // error: static_cast cannot perform this conversion
char* p = reinterpret_cast<char*>(&x); // OK
*p = 'z'; // maybe this modifies x (see below)
El resultado de reinterpret_cast
representa la misma dirección que el operando, siempre que la dirección esté alineada adecuadamente para el tipo de destino. De lo contrario, el resultado no se especifica.
int x = 42;
char& r = reinterpret_cast<char&>(x);
const void* px = &x;
const void* pr = &r;
assert(px == pr); // should never fire
El resultado de reinterpret_cast
no está especificado, excepto que un puntero (referencia respectiva) sobrevivirá un viaje de ida y vuelta desde el tipo de origen al tipo de destino y viceversa, siempre que el requisito de alineación del tipo de destino no sea más estricto que el del tipo de origen.
int x = 123;
unsigned int& r1 = reinterpret_cast<unsigned int&>(x);
int& r2 = reinterpret_cast<int&>(r1);
r2 = 456; // sets x to 456
En la mayoría de las implementaciones, reinterpret_cast
no cambia la dirección, pero este requisito no se estandarizó hasta C ++ 11.
reinterpret_cast
también se puede utilizar para convertir de un tipo de puntero a datos a otro, o un tipo de función de puntero a miembro a otro.
El uso de reinterpret_cast
se considera peligroso porque la lectura o escritura a través de un puntero o una referencia obtenida mediante reinterpret_cast
puede desencadenar un comportamiento indefinido cuando los tipos de origen y destino no están relacionados.
Conversión entre puntero y entero
Un puntero de objeto (incluido void*
) o un puntero de función se puede convertir a un tipo entero usando reinterpret_cast
. Esto solo se compilará si el tipo de destino es lo suficientemente largo. El resultado está definido por la implementación y generalmente proporciona la dirección numérica del byte en memoria a la que apuntan los punteros.
Por lo general, long
o unsigned long
es lo suficientemente largo para contener cualquier valor de puntero, pero esto no está garantizado por la norma.
Si los tipos std::intptr_t
y std::uintptr_t
existen, se garantiza que son lo suficientemente largos para contener un void*
(y, por lo tanto, cualquier puntero al tipo de objeto). Sin embargo, no se garantiza que sean lo suficientemente largos para contener un puntero de función.
De manera similar, reinterpret_cast
se puede usar para convertir un tipo entero en un tipo de puntero. Nuevamente, el resultado está definido por la implementación, pero se garantiza que un valor de puntero no se modificará en un viaje de ida y vuelta a través de un tipo entero. El estándar no garantiza que el valor cero se convierta en un puntero nulo.
void register_callback(void (*fp)(void*), void* arg); // probably a C API
void my_callback(void* x) {
std::cout << "the value is: " << reinterpret_cast<long>(x); // will probably compile
}
long x;
std::cin >> x;
register_callback(my_callback,
reinterpret_cast<void*>(x)); // hopefully this doesn't lose information...
Conversión por constructor explícito o función de conversión explícita
Una conversión que implique llamar a un constructor explícito o una función de conversión no se puede hacer de manera implícita. Podemos solicitar que la conversión se realice explícitamente usando static_cast
. El significado es el mismo que el de una inicialización directa, excepto que el resultado es temporal.
class C {
std::unique_ptr<int> p;
public:
explicit C(int* p) : p(p) {}
};
void f(C c);
void g(int* p) {
f(p); // error: C::C(int*) is explicit
f(static_cast<C>(p)); // ok
f(C(p)); // equivalent to previous line
C c(p); f(c); // error: C is not copyable
}
Conversión implícita
static_cast
puede realizar cualquier conversión implícita. Este uso de static_cast
ocasionalmente puede ser útil, como en los siguientes ejemplos:
Cuando se pasan argumentos a una elipsis, el tipo de argumento "esperado" no se conoce estáticamente, por lo que no se producirá una conversión implícita.
const double x = 3.14; printf("%d\n", static_cast<int>(x)); // prints 3 // printf("%d\n", x); // undefined behaviour; printf is expecting an int here // alternative: // const int y = x; printf("%d\n", y);
Sin la conversión explícita de tipos, se pasaría un objeto
double
a los puntos suspensivos y se produciría un comportamiento indefinido.Un operador de asignación de clase derivada puede llamar a un operador de asignación de clase base de la siguiente manera:
struct Base { /* ... */ }; struct Derived : Base { Derived& operator=(const Derived& other) { static_cast<Base&>(*this) = other; // alternative: // Base& this_base_ref = *this; this_base_ref = other; } };
Enum las conversiones
static_cast
puede convertir de un tipo de punto flotante o entero a un tipo de enumeración (ya sea con o sin ámbito), y viceversa. También puede convertir entre tipos de enumeración.
- La conversión de un tipo de enumeración sin ámbito a un tipo aritmético es una conversión implícita; es posible, pero no necesario, usar
static_cast
.
Cuando un tipo de enumeración de ámbito se convierte en un tipo aritmético:
- Si el valor de la enumeración se puede representar exactamente en el tipo de destino, el resultado es ese valor.
- De lo contrario, si el tipo de destino es un tipo entero, el resultado no se especifica.
- De lo contrario, si el tipo de destino es un tipo de punto flotante, el resultado es el mismo que el de la conversión al tipo subyacente y luego al tipo de punto flotante.
Ejemplo:
enum class Format { TEXT = 0, PDF = 1000, OTHER = 2000, }; Format f = Format::PDF; int a = f; // error int b = static_cast<int>(f); // ok; b is 1000 char c = static_cast<char>(f); // unspecified, if 1000 doesn't fit into char double d = static_cast<double>(f); // d is 1000.0... probably
Cuando un entero o tipo de enumeración se convierte en un tipo de enumeración:
- Si el valor original está dentro del rango de la enumeración de destino, el resultado es ese valor. Tenga en cuenta que este valor puede ser desigual para todos los enumeradores.
- De lo contrario, el resultado es no especificado (<= C ++ 14) o indefinido (> = C ++ 17).
Ejemplo:
enum Scale { SINGLE = 1, DOUBLE = 2, QUAD = 4 }; Scale s1 = 1; // error Scale s2 = static_cast<Scale>(2); // s2 is DOUBLE Scale s3 = static_cast<Scale>(3); // s3 has value 3, and is not equal to any enumerator Scale s9 = static_cast<Scale>(9); // unspecified value in C++14; UB in C++17
Cuando un tipo de punto flotante se convierte en un tipo de enumeración, el resultado es el mismo que la conversión al tipo subyacente de la enumeración y luego al tipo de enumeración.
enum Direction { UP = 0, LEFT = 1, DOWN = 2, RIGHT = 3, }; Direction d = static_cast<Direction>(3.14); // d is RIGHT
Derivado a conversión base para punteros a miembros
Un puntero a miembro de clase derivada se puede convertir en un puntero a miembro de clase base usando static_cast
. Los tipos apuntados deben coincidir.
Si el operando es un puntero nulo al valor miembro, el resultado también es un puntero nulo al valor miembro.
De lo contrario, la conversión solo es válida si el miembro al que apunta el operando existe realmente en la clase de destino, o si la clase de destino es una clase base o derivada de la clase que contiene el miembro al que apunta el operando. static_cast
no comprueba la validez. Si la conversión no es válida, el comportamiento no está definido.
struct A {};
struct B { int x; };
struct C : A, B { int y; double z; };
int B::*p1 = &B::x;
int C::*p2 = p1; // ok; implicit conversion
int B::*p3 = p2; // error
int B::*p4 = static_cast<int B::*>(p2); // ok; p4 is equal to p1
int A::*p5 = static_cast<int A::*>(p2); // undefined; p2 points to x, which is a member
// of the unrelated class B
double C::*p6 = &C::z;
double A::*p7 = static_cast<double A::*>(p6); // ok, even though A doesn't contain z
int A::*p8 = static_cast<int A::*>(p6); // error: types don't match
nulo * a T *
En C ++, void*
no se puede convertir implícitamente a T*
donde T
es un tipo de objeto. En su lugar, static_cast
debe usarse para realizar la conversión explícitamente. Si el operando realmente apunta a un objeto T
, el resultado apunta a ese objeto. De lo contrario, el resultado no se especifica.
Incluso si el operando no apunta a un objeto T
, siempre que el operando apunte a un byte cuya dirección esté correctamente alineada para el tipo T
, el resultado de la conversión apunta al mismo byte.
// allocating an array of 100 ints, the hard way
int* a = malloc(100*sizeof(*a)); // error; malloc returns void*
int* a = static_cast<int*>(malloc(100*sizeof(*a))); // ok
// int* a = new int[100]; // no cast needed
// std::vector<int> a(100); // better
const char c = '!';
const void* p1 = &c;
const char* p2 = p1; // error
const char* p3 = static_cast<const char*>(p1); // ok; p3 points to c
const int* p4 = static_cast<const int*>(p1); // unspecified in C++03;
// possibly unspecified in C++11 if
// alignof(int) > alignof(char)
char* p5 = static_cast<char*>(p1); // error: casting away constness
Casting estilo c
El casting de estilo C se puede considerar el casting de "Mejor esfuerzo" y se denomina así porque es el único casting que se podría usar en C. La sintaxis de este casting es la (NewType)variable
.
Cuando se utiliza este lanzamiento, usa uno de los siguientes lanzamientos de c ++ (en orden):
-
const_cast<NewType>(variable)
-
static_cast<NewType>(variable)
-
const_cast<NewType>(static_cast<const NewType>(variable))
-
reinterpret_cast<const NewType>(variable)
-
const_cast<NewType>(reinterpret_cast<const NewType>(variable))
La conversión funcional es muy similar, aunque como pocas restricciones como resultado de su sintaxis: NewType(expression)
. Como resultado, solo se pueden convertir los tipos sin espacios.
Es mejor usar el nuevo c ++ cast, porque es más legible y se puede detectar fácilmente en cualquier lugar dentro de un código fuente de C ++ y los errores se detectarán en tiempo de compilación, en lugar de en tiempo de ejecución.
Como este lanzamiento puede dar lugar a reinterpret_cast
no deseado, a menudo se considera peligroso.