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?

 8
Author: Kahler, 2017-02-19

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 and reinterpret_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 pra int).
    • call a constructor of novo_tipo through the result of expressão.
    • use user-defined conversion operator from the result of expressão to the novo_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.)
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 of novo_tipo), behaves as an implicit conversion.
    • when converting pointers down into the hierarchy, (expressão is base of novo_tipo), checks whether expressão originally referred to a pointer to novo_tipo and, if so, returns the set pointer. If the check fails, Returns nullptr.
    • conversion between references is similar, but throws exception std::bad_cast in case of failure.
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 only const_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 from novo_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:

  1. const_cast.
  2. static_cast, bypassing restricted access case used in class hierarchies.
  3. static_cast followed by const_cast
  4. reinterpret_cast
  5. reinterpret_cast followed by const_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

 12
Author: Kahler, 2019-03-01 01:09:36