C++ conversion: what is the difference between static cast, dynamic cast, const cast and reinterpret cast?
What is the difference between the castings present in C++?
There are the static_cast
, dynamic_cast
, const_cast
and reinterpret_cast
, What is the difference between these? When to use each?
C++ also supports cast in the style of the C language, how is it interpreted by the compiler?
1 answers
The importance of understanding type conversion
C++ is a language called strictly typed, a term usually translated as strongly typed, this means that the type of variables is always right and the operations must be defined for specific types. Generating an object of one type through an expression of another type involves a conversion, or casting .
How to just explain what each conversion does may not to be very enlightening, I added simple examples to the end.
There are the
static_cast
,dynamic_cast
,const_cast
andreinterpret_cast
, What is the difference between these?
static_cast<novo_tipo>(expressão);
-
Static_cast, static conversion
This is the most common conversion. It is said static because its validity is analyzed during compilation, the main possibilities are:
- implicit conversion between types (such as
float
praint
). - call a constructor of
novo_tipo
through the result ofexpressão
. - use user-defined conversion operator from the result of
expressão
to thenovo_tipo
. - convert pointers between class hierarchy, as long as the classes are not virtual. (
static_cast
does not check the validity of the conversion during execution.) - convert pointers
void*
to any other type of pointer. (undefined result if pointer alignment is not That's correct.)
- implicit conversion between types (such as
dynamic_cast<novo_tipo>(expressão);
-
Dynamic_cast, dynamic conversion
This conversion is special for references or pointers to polymorphic objects (classes containing virtual functions). It is called dynamic because it checks during the execution of the program if the conversion is valid when descending in the hierarchy of classes. In main:
- when converting pointers to top in the hierarchy (
expressão
is derived ofnovo_tipo
), behaves as an implicit conversion. - when converting pointers down into the hierarchy, (
expressão
is base ofnovo_tipo
), checks whetherexpressão
originally referred to a pointer tonovo_tipo
and, if so, returns the set pointer. If the check fails, Returnsnullptr
. - conversion between references is similar, but throws exception
std::bad_cast
in case of failure.
- when converting pointers to top in the hierarchy (
const_cast<novo_tipo>(expressão);
-
Const_cast , constancy conversion
This conversion has the sole function of adding or removing the property
const
. Any conversion can generate a reference or Pointer for an object to be treated as constant, but onlyconst_cast
can generate a new reference or Pointer for a constant object to be treated as modifiable (non-constant). This conversion does not generate any statements, it is only a directive for the compiler.
reinterpret_cast<novo_tipo>(expressão);
-
Reinterpret_cast, conversion by reinterpretation
This conversion is more distant from the characteristic strongly typed of the C++ language, since it commands the compiler to reinterpret the result of
expressão
as if it were fromnovo_tipo
, in general without performing any operation or checking on the values being converted. Roughly speaking,reinterpret_cast
is a way of telling the compiler: "trust me, these numbers I'm passing you are what I say they are" . Its main uses are:- convert pointer or reference for any type of object to pointer or reference for any other type of object.
- convert a pointer to an integer.
- convert an integer to pointer.
When to use each?
Try to use conversion that best expresses your intentions. Since only the definition of each type of conversion may not be very elucidating, here are examples of each:
- static_cast
A static_cast
is the most common and usually occurs implicitly in an implicit conversion, as in the example:
int i = 5;
float x = i; //conversão implícita
float y = static_cast<float>(i); //conversão explícita
Implicit conversions make it easy to handle numeric and local variables, but it is always recommended to make the conversion explicit when exporting the variable for some function. In the example below, the result with and without cast
is the same (soon after I will point out a possible problem):
int f(int x)
{
return x*2;
}
int main()
{
float x = 1.f;
std::cout << "Sem cast : " << f(x) << std::endl;
std::cout << "Com cast : " << f(static_cast<int>(x)) << std::endl;
}
Result:
Sem cast : 2
Com cast : 2
Now, let's say a new function is introduced, without changing the existing parts of the previous code:
int f(double x)
{
return x*5;
}
The result of the program changes:
Sem cast : 5
Com cast : 2
The reason for this is that the implicit conversion from float
to double
is preferred over the implicit conversion from float
to int
.
Although required in some cases (such as converting pointers void*
to other types) static_cast
has a function that hangs more for good code organization and maintenance.
- dynamic_cast
dynamic_cast
it is the way to check at runtime if the type of polymorphic object passed is of a certain type (for this the program makes use of RTTI, I will omit details). In a simplified example, imagine a base class with two types of derivatives:
struct Base{
virtual ~Base(){};
};
struct DerivadaA : public Base
{};
struct DerivadaB : public Base
{};
Then let's say we have a function that should handle objects of Type Base
,
void f(Base* ponteiro_base)
But at some point the execution should be different if the passed object is DerivadaA
or DerivadaB
. Since dynamic_cast
Returns nullptr
in case of failure, we can do the following:
void f(Base* ponteiro_base)
{
//tenta cast para ponteiro do tipo DerivadaA
DerivadaA * objA = dynamic_cast<DerivadaA*>(ponteiro_base);
if(objA != nullptr)
{
std::cout<<"Objeto do tipo A" << std::endl;
return;
}
//tenta cast para ponteiro do tipo DerivadaB
DerivadaB * objB = dynamic_cast<DerivadaB*>(ponteiro_base);
if(objB != nullptr)
{
std::cout<<"Objeto do tipo B" << std::endl;
return;
}
}
Click here to see the above example in action .
- const_cast
Removing the const
property from an object is and modifying a declared object const
results in undefined program behavior, so it's up to the programmer to cohesively use this conversion.
An illustrative example: when you want to return a reference to a member of a class (the famous setters
and getters
):
class ClasseX
{
int X;
public:
//retorna referência a membro da classe
int& getRefX()
{
return X;
};
};
Since the function does not change the state of the Class, I may want to mark it const
:
int& getRefX() const
But hence the code does not compile (the const
signature of the function makes its members const
within this)...
error: binding 'const int' to reference of type 'int&' discards qualifiers
const_cast
can be used to return the non-constant reference:
//retorna referência a membro da classe
int& getRefX()
{
//(algum comentário explicando o const_cast)
return const_cast<int&>(X);
};
const_cast
it should be used very carefully, since changing the value of an originally constant variable makes the program poorly formed (undefined behavior). In the example above, if some object of Class ClasseX
was originally constant, the function would still work in this one, but the value of X
should not be modified via the reference return.
- reinterpret_cast
reinterpret_cast
moves away from the notion of objects and approaches the notion of bits (information).
For example, let's say you are programming a microcontroller and in the manual of this says that the sound card reads the information from the address 33
in memory:
//endereço obtido do manual do microchip
static const int ADDR_PLACA_DE_SOM = 33;
To write information in this area of memory, you need to convert this position to a pointer, with reinterpret_cast
you can do it:
//cria ponteiro para inteiros na posição dita pelo manual
int* som_pt = reinterpret_cast<int*>(ADDR_PLACA_DE_SOM);
Note that the conversion is quite free, you can create a pointer to any type of data:
//cria ponteiro para chars na posição dita pelo manual
char* som_pt = reinterpret_cast<char*>(ADDR_PLACA_DE_SOM);
C++ also supports cast in the style of the C language, how is it interpreted by the compiler?
The -C style conversions are of the type:
(novo_tipo)(expressão)
An example:
//gera um inteiro através do float 3.14
(int)(3.14f)
This type of conversion is present in C++ mainly for language compatibility reasons C.
The compiler tries the following order of conversions when a cast in the-C style is used:
-
const_cast
. -
static_cast
, bypassing restricted access case used in class hierarchies. -
static_cast
followed byconst_cast
reinterpret_cast
-
reinterpret_cast
followed byconst_cast
That is, the compiler tries almost everything to generate an object of the new type, including removing const
. This type of conversion can generate unwanted conversions (up to non-object oriented reinterpret_cast
!) that hide bugs propagable by the logic of the program.
This answer is a simplification. I reiterate that understanding object conversion well indicates a good understanding of the object orientation paradigm. The main references were:
Cppreference: static_cast, dynamic_cast, const_cast, reinterpret_cast, Explicit type conversion