Programowanie w języku C/C++

Reinterpret cast

  • 2009-07-28 20:19
  • 0 komentarzy
  • 7924 odsłony
  • 5/6
Postaram się wytłumaczyć sposób działania reinterpret_cast, posiłkując się moim tłumaczeniem opisu zaczerpniętego z Helpa Borlanda jak i własnymi doświadczeniami, co do możliwości używania tego operatora.

reinterpret_cast< T > (arg) to operator, którego celem jest konwersja przez zamianę typów, które są niepewne lub zależne od implementacji.

W deklaracji, reinterpret_cast< T > (arg) , T musi być wskaźnikiem, referencją, typem arytmetycznym, wskaźnikiem na funkcję, lub wskaźnikiem na element.

Wskaźnik może być całkowicie przekonwertowany na typ wbudowany.

Wbudowany arg może być przekonwertowany na wskaźnik. Konwersja wskaźnika na typ wbudowany i na odwrót na ten sam typ wskaźnikowy dostarcza oryginalną wartość.

Możliwe jest użycie do konwersji, jeszcze nie zdefiniowanej klasy wskaźnika lub referencji.

Wskaźnik na funkcje może być poprawnie przekonwertowany na wskaźnik na obiekt pod warunkiem, że dostarczany wskaźnik na obiekt, posiada wystarczającą ilość bitów do przechowania wskaźnika na funkcję. Wskaźnik na obiekt może być poprawnie przekonwertowany na wskaźnik na funkcję tylko jeśli wskaźnik na funkcję jest wystarczająco duży aby przechować wskaźnik na obiekt.

Po tak fantastycznej definicji, należałoby wyjaśnić co nieco. Zacznijmy od wyjaśnienia pojęcia "niepewne". Otóż przez niepewne rozumiemy takie przy których nie mamy pewności co do konwersji niejawnej dokonanej przez kompilator. Przykładem takim są typy int i char. Jak wiadomo w języku C/C++ oba te typy można traktować zamiennie. Aby zabezpieczyć się przed niepoprawną konwersją tych typów, możemy posłużyć się operatorem  reinterpret_cast, za pomocą którego możemy narzucić kompilatorowi typ jakiego porządamy.

Jak można zauważyć jest to operator szablonowy, wykorzystujący parametr <T> (typowy dla szablonów), czyli klasę szablonową. Stąd w definicji operatora pojawił się typowy opis co do właściwości klasy T.

Operator ten pozwala na dokonanie konwersji na typ, którego definicja jeszcze nie została umieszczona, aczkolwiek nie zmienia to faktu, że deklaracja musi istnieć.

Co do wskaźników na obiekt i funkcję, wyjaśnię tylko tyle, że w przypadku błędów konwersji spowodowanych zbyt małą ilością zarezerwowanej przestrzeni w pamięci dojść może do tego, że w rezultacie dostaniemy jakieś mało istotne wartości (śmieci). Tak wiec należy zwracać uwagę aby konwertowane typy były reprezentowane na tej samej ilości bitów, gdyż tak naprawdę nie mamy do czynienia z konwersją w sensie konwersji liczby float na int (gdzie część ułamkowa zostaje odrzucona, a wynik jest tylko częścią całkowitą), a reinterpretacją tego na co wskazuje wskaźnik, na którym operujemy.

Jako przykład posłuży fragment kodu programu operującego na strumieniach wejścia-wyjścia przy zapisie do pliku.

// deklaracja klasy figura
class Figura { /* ... */ };
 
// ...
 
// powołanie instancji klasy figura
Figura p1, p2; 
 
// zapis do pliku przeinterpretowanego (przekonwertowanego) obiektu klasy Figura na ciąg znaków
Plikwy.write( reinterpret_cast<const char*>(&p1), sizeof(Figura));
 
// odczyt z pliku ciąg znaków i przeinterpretowanie go obiekt klasy Figura
Plikwe.read ( reinterpret_cast<char*>(&p2), sizeof(Figura));
 
// ...

Ten fragment kodu pokazuje w jak prosty sposób można stworzyć prosty mechanizm Serializacji danych. Pozwala nam to na zapis do jednego pliku obiektów różnych typów, będących w relacji generalizacji (prościej dziedziczących po jednym rodzicu). W tym przypadku fragment kodu miałby postać:

// deklaracja klasy figura
class Figura { /* ... */ };
 
// deklaracja klasy dziecka Prostokat
class Prostakat: public Figura { /* ... */ };
 
// deklaracja klasy dziecka Kolo
class Kolo: public Figura { /* ... */ };
 
// ...
 
// powołanie instancji klas
Prostakat p1;
Kolo p2; 
 
// zapis do pliku przeinterpretowanego (przekonwertowanego) obiektu klasy Prostakat na ciąg znaków
Plikwy.write( reinterpret_cast<const char*>(&p1), sizeof(Figura));
 
// odczyt z pliku ciąg znaków i przeinterpretowanie go obiekt klasy Prostakat
Plikwe.read ( reinterpret_cast<char*>(&p1), sizeof(Figura));
 
// zapis do pliku przeinterpretowanego (przekonwertowanego) obiektu klasy Kolo na ciąg znaków
Plikwy.write( reinterpret_cast<const char*>(&p2), sizeof(Figura));
 
// odczyt z pliku ciąg znaków i przeinterpretowanie go obiekt klasy Kolo
Plikwe.read ( reinterpret_cast<char*>(&p2), sizeof(Figura));
 
// ...

Taki mechanizm pozwala na umieszczenie w jednym pliku wielu obiektów dziedziczących po jednym rodzicu. Wykorzystujemy tutaj omawianą w definicji klasę bez definicji którą jest klasa Figura, a dzięki mechanizmowi polimorfizmu, reinterpretacji dokonujemy poprzez klasę rodzica na klasy podrzędne, czyli dziecko. W przypadku złej kolejności odczytu tak zapisanych danych, logicznym jest że dostaniemy błędne wyniki, gdyż definicje klas dzieci mogą, a raczej na pewno zawierają znaczne różnice, chociażby co do liczby parametrów.