Conversion to C++: What is the difference between static_cast, dynamic_cast, const_cast and reinterpret_cast?

Asked

Viewed 3,505 times

9

What is the difference between casting present in the C++?

There is 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, as it is interpreted by the compiler?

1 answer

13

The importance of understanding the conversion of types

C++ is a spoken language Strictly typed, term usually translated as heavily 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.

As just explaining what each conversion does may not be very revealing, I added simple examples at the end.


There is 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 for int).
    • Call a builder of novo_tipo through the outcome of expressão.
    • Use user-defined conversion operator of 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* for any other type of pointer. (undefined result if pointer alignment is not 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 class hierarchy. In main:

    • When converting pointers up in the hierarchy (expressão is derived from novo_tipo), behaves as an implicit conversion.
    • When converting pointers down in the hierarchy, (expressão is the basis of novo_tipo), checks whether expressão originally referred to a pointer to novo_tipo and, if yes, returns the adjusted pointer. If the check fails, it returns nullptr.
    • Conversion between references is similar, but generates exception std::bad_cast in case of failure.
const_cast<novo_tipo>(expressão);
  • const_cast, constancy conversion

    This conversion has the only function of adding or removing the property const. Any conversion can generate a reference or pointer to an object so that it is treated as constant, but only const_cast can generate a new reference or pointer to a constant object to be treated as modifiable (nonconstant). This conversion does not generate any instruction, it is only a directive for the compiler.

reinterpret_cast<novo_tipo>(expressão);
  • reinterpret_cast, conversion by reinterpretation

    This conversion is that more distance from the characteristic heavily typed of the C++ language, as 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 verification on the values being converted. Roughly, reinterpret_cast is way of telling the compiler: "Trust me, these numbers I’m giving you are what I say they are" . Its main uses are:

    • Convert pointer or reference to any type of object to pointer or reference to any other type of object.
    • Convert a pointer to an integer number.
    • Convert an integer number to pointer.

When to use each?

Try to use the conversion that best expresses your intentions. As only the definition of each type of conversion may not be very elucidative, follow examples of each:

  • static_cast

To 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 to some function. In the example below, the result with and without the 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;
}

Upshot:

Sem cast : 2
Com cast : 2

Now, 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 implied conversion of float for double is preferred over the implicit conversion of float for int.

Although required in some cases (such as converting pointers void* for other types) static_cast has a function that depends more on good organization and maintenance of the code.

  • dynamic_cast

dynamic_cast is the way to check at runtime whether 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 derivative types:

struct Base{
    virtual ~Base(){};
};

struct DerivadaA : public Base
{};

struct DerivadaB : public Base
{};

Let’s say we have a function that must deal with objects of the type Base,

void f(Base* ponteiro_base)

But at some point the execution must be different if the object passed is DerivadaA or DerivadaB. Like 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 example above in action.

  • const_cast

Remove the property const of an object is rare need, and modify a declared object const results in undefined program behavior, so it is up to the programmer to use this conversion cohesively.

An illustrative example: When one wishes 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 class state, I may want to mark it const:

int& getRefX() const

But hence the code does not compile (the signature const 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 should be used very carefully, as changing the value of a originally constant variable makes the program poorly formed (Undefined behaviour). In the example above, if any object of the class ClasseX was originally constant, the function would still work in this, but the value of X should not be modified by the reference returned.

  • reinterpret_cast

The reinterpret_cast moves away from the notion of objects and approaches the notion of bits (information).

For example, let’s say you’re programming a microcontroller and the manual 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, as it is interpreted by the compiler?

As 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 compatibility with the C language.

The compiler attempts the following order of conversions when a cast in C-style is used:

  1. const_cast.
  2. static_cast, ignoring restricted access if 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 a new type object, including removing const. This type of conversion can generate unwanted conversions (up to the not object-oriented reinterpret_cast!) that hide bugs that can be propagated by the logic of the program.


This answer is a simplification. I reiterate that to understand well the conversion of objects indicates a good understanding of the object orientation paradigm. The main references were:

cppreference (in English): static_cast, dynamic_cast, const_cast, reinterpret_cast, Explicit type Conversion

Browser other questions tagged

You are not signed in. Login or sign up in order to post.