W czystym C, tak jak w Javie, możemy zażądać jawnie konwersji od wartości jednego typu do wartości innego typu za pomocą rzutowania. Ma ono postać
(Typ) wyrazeniegdzie Typ jest nazwą typu, a wyrazenie jest wyrażeniem o p-wartości innego typu. Wynikiem jest p-wartość typu Typ, reprezentująca wartość wyrażenia wyrazenie. W Javie tego typu rzutowania są bezpieczne: albo na etapie kompilacji, albo, jeśli to niemożliwe, na etapie wykonania zostanie sprawdzone, czy takie rzutowanie ma sens. W C taka forma rzutowania jest mniej bezpieczna; będzie ono wykonane „siłowo”, czasem zupełnie bezsensownie. Dlatego lepiej jest używać nowych operatorów, wprowadzonych w C++, które wykonują te konwersje w sposób bardziej kontrolowany. Wszystkie one mają postać
rodzaj_cast<Typ>(wyrazenie)gdzie zamiast ' rodzaj' należy wstawić static, dynamic, const lub reinterpret. Wynikiem będzie p-wartość typu Typ utworzona na podstawie wartości wyrażenia wyrazenie.
Konwersja uzmienniająca ma postać
const_cast<Typ>(wyrazenie)Wyrażenie wyrazenie musi tu być tego samego typu co Typ, tylko z modyfikatorem const lub volatile. A zatem tego rodzaju konwersja usuwa „ustaloność” (lub „ulotność”) i może służyć tylko do tego celu. Z drugiej strony, tego samego efektu nie można uzyskać za pomocą konwersji przy użyciu static_cast, dynamic_cast lub reinterpret_cast: kompilator uznałby taką konwersję const Typ → Typ za nielegalną. Rozpatrzmy przykład:
      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  void changeFirst(char* str, char c) {
      5.      str[0]=c;
      6.  }
      7.  
      8.  int main() {
      9.      const char name[] = "Jenny";
     10.      cout << name << endl;
     11.  
     12.      // name[0]='K';
     13.  
     14.      changeFirst(const_cast<char*>(name),'K');
     15.  
     16.      // changeFirst(name,'K');
     17.  
     18.      cout << name << endl;
     19.  }
    Jenny
    Kenny
po tej operacji zmiana ustalonego napisu powiodła się. Zauważmy
też, że bez konwersji, a więc tak jak w wykomentowanej linii 16,
wywołać tej funkcji nie byłoby można, bo
name
 jest
C-napisem ustalonym, a funkcja
changeFirst
 nie „obiecuje”,
poprzez deklarację typu parametru jako
const, że napisu
przekazanego jako argument nie zmieni (czego zresztą obiecać nie
może, bo właśnie ten napis zmienia).
Jeśli deklarujemy zmienne ustalone, to robimy to właśnie po to, aby ich nie można było zmieniać. Zatem użycie konwersji uzmienniającej świadczy o jakiejś niekonsekwencji w programie. Powinno być zatem stosowane tylko w wyjątkowych wypadkach.
static_cast<Typ>(wyrazenie)i dokonuje jawnego przekształcenia typu, sprawdzając, czy jest to przekształcenie dopuszczalne. Sprawdzenie odbywa się podczas kompilacji. Często użycie tego operatora jest właściwie zbędne, bo konwersja i tak zostanie dokonana. Jeśli jest to jednak konwersja, w której może wystąpić utrata informacji, to kompilator zwykle ostrzega nas przed jej użyciem. Stosując jawną konwersję statyczną, unikamy tego rodzaju ostrzeżeń kompilatora. Na przykład kompilacja prawidłowego fragmentu kodu
       double x = 4;
       int i = x;
spowoduje wysłanie ostrzeżeń kompilatora
    d.cpp:6: warning: initialization to `int' from `double'
    d.cpp:6: warning: argument to `int' from `double'
których możemy uniknąć jawnie dokonując konwersji:
       double x = 4;
       int i = static_cast<int>(x);
Częstym zastosowaniem rzutowania statycznego jest rzutowanie
od typu
void*
 do typu
Typ*
 (konwersja w drugą
stronę jest zawsze bezpieczną konwersją standardową, która
nie wymaga sprawdzania, więc nie musi być jawna).
Takie konwersje stosuje się także do rzutowaia w dół wskaźników
typu „wskaźnik do obiektu klasy bazowej” do typu
„wskaźnik do obiektu klasy pochodnej” (dla typów niepolimorficznych,
o czym powiemy w dalszej części).
Konwersja dynamiczna ma postać
dynamic_cast<Typ>(wyrazenie)Konwersje dynamiczne stosuje się, gdy prawidłowość przekształcenia nie może być sprawdzona na etapie kompilacji, bo zależy od typu obiektu klasy polimorficznej. Typ ten jest znany dopiero w czasie wykonania i wtedy ma miejsce sprawdzenie poprawności. Tego rodzaju konwersje używane są wyłącznie w odniesieniu do klas polimorficznych i tylko dla typów wskaźnikowych i referencyjnych. Ponieważ o polimorfizmie jeszcze nie mówiliśmy, pozostawimy dalsze szczegóły do rozdziału o dynamicznym rzutowaniu .
„Najsilniejszą” formą konwersji jest konwersja wymuszana. Ma ona postać
reinterpret_cast<Typ>(wyrazenie)Użycie takiej konwersji oznacza, że rezygnujemy ze sprawdzania jej poprawności w czasie kompilacji i wykonania i, co za tym idzie, ponosimy pełną odpowiedzialność za jej skutki. Stosuje się ją, gdy wiemy z góry, że ani w czasie kompilacji, ani w czasie wykonania nie będzie możliwe określenie jej sensowności. W ten sposób można, na przykład, dokonać konwersji char* → int* lub klasaA* → klasaB*, gdzie klasy klasaA i klasaB są zupełnie niezależne. Takie konwersje nie są bezpieczne, a ich reaultat może zależeć od używanej platformy czy kompilatora.
Przyjrzyjmy się na przykład poniższemu programowi:
      1.  #include <iostream>
      2.  #include <cstring>
      3.  #include <fstream>
      4.  using namespace std;
      5.  
      6.  class Person {
      7.      char nam[30];
      8.      int  age;
      9.  public:
     10.      Person(const char* n, int a) : age(a) {
     11.          strcpy(nam,n);
     12.      }
     13.  
     14.      void info() {
     15.          cout << nam << " (" << age << ")" << endl;
     16.      }
     17.  };
     18.  
     19.  int main() {
     20.      const size_t size = sizeof(Person);
     21.  
     22.      Person john("John Brown",40);
     23.      Person mary("Mary Wiles",26);
     24.  
     25.      ofstream out("person.ob");
     26.      out.write(reinterpret_cast<char*>(&john),size);
     27.      out.write(                (char*) &mary ,size);
     28.      out.close();
     29.  
     30.      char* buff1 = new char[size];
     31.      char* buff2 = new char[size];
     32.      ifstream in("person.ob");
     33.      in.read(buff1,size);
     34.      in.read(buff2,size);
     35.      in.close();
     36.  
     37.      Person* p1 = reinterpret_cast<Person*>(buff1);
     38.      Person* p2 =                 (Person*) buff2 ;
     39.  
     40.      p1->info();
     41.      p2->info();
     42.  
     43.      delete [] buff1;
     44.      delete [] buff2;
     45.  }
Dwa obiekty klasy Person zapisane na dysk odczytujemy następnie do dwóch tablic znakowych buff1 i buff2 (linie 33 i 34). Po wczytaniu są to po prostu tablice znaków (bajtów): ani w czasie kompilacji, ani w czasie wykonania system nie ma możliwości sprawdzenia, czy zawarte w nich ciągi bajtów rzeczywiście są reprezentacją obiektów klasy Person. Ale my wiemy, że powinno tak być, bo sami przed chwilą te ciągi bajtów zapisaliśmy. Wymuszamy zatem (linie 37 i 38) konwersję zmiennych buff do typu Person* — znów na dwa sposoby: raz za pomocą reinterpret_cast i raz za pomocą rzutowania w stylu C. Wydruk z linii 40 i 41
    John Brown (40)
    Mary Wiles (26)
przekonuje nas, że konwersja się udała. Pamiętać jednak trzeba,
że karkołomne konwersje wymuszane nie zawsze dają rezultaty zgodne
z oczekiwaniem. Co gorsza, rezultaty te mogą zależeć od użytego
kompilatora i architektury komputera: skoro świadomie zrezygnowaliśmy
z kontroli typów, język nie daje nam tu żadnych gwarancji. Tworzona
jest wartość, która ma wzorzec bitowy taki jak wartość
konwertowana, ale przypisany jest jej inny typ: sensowność tego nie
jest ani zapewniana, ani sprawdzana.
T.R. Werner, 21 lutego 2016; 20:17