Scott Meyers
"J臋zyk C++ bardziej efektywny"
Scott Meyers
揓臋zyk C++ bardziej efektywny"
Punkt 2. Daj pierwsze艅stwo rzutowaniom w stylu j臋zyka C++
Zajmijmy si臋 ulubionym rzutowaniem. Chocia偶 jest ono prawie takim samym pariasem programowania jak instrukcja goto, to jednak trwa nadal, poniewa偶 kiedy dochodzi do najgorszego i nie ma innego wyj艣cia, wtedy u偶ycie rzutowania mo偶e si臋 okaza膰 ostatni膮 desk膮 ratunku. W pewnych okoliczno艣ciach rzutowanie jest po prostu niezb臋dne.
Jednak rzutowania w stylu j臋zyka C nie s膮 niczym przyjemnym. Po pierwsze s膮 to raczej prymitywne bestie, kt贸re pozwalaj膮 Ci rzutowa膰 niemal偶e z ka偶dego typu do dowolnego innego typu. By艂oby mi艂o, gdyby mo偶na by1o precyzyjniej okre艣la膰 cel ka偶dego rzutowania. Na przyk艂ad istnieje wielka r贸偶nica mi臋dzy rzutowaniem, kt贸re wska藕nik do obiektu maj膮cego typ z modyfikatorem const zast臋puje wska藕nikiem do obiektu bez modyfikatora const (tj. rzutowaniem usuwaj膮cym tylko w艂a艣ciwo艣膰 const danego obiektu), a rzutowaniem, kt贸re wska藕nik do obiektu klasy podstawowej zast臋puje wska藕nikiem do obiektu klasy pochodnej (tj. rzutowaniem ca艂kowicie zmieniaj膮cym typ obiektu) . W tradycyjnym rzutowaniu w stylu j臋zyka C nie ma takiego rozr贸偶nienia. (Trudno si臋 temu dziwi膰 - przecie偶 rzutowanie w stylu j臋zyka C nie by艂o przeznaczone dla C++, lecz dla C).
Drugi k艂opot zwi膮zany z rzutowaniami polega na tym, 偶e nie艂atwo je znale藕膰 w programie. Pod wzgl臋dem syntaktycznym na rzutowanie sk艂ada si臋 niewiele wi臋cej ni偶 para nawias贸w oraz identyfikator, lecz identyfikatory uj臋te w nawiasy wyst臋puj膮 wsz臋dzie w programach w j臋zyku C++. Z tego powodu trudno odpowiedzie膰 nawet na najbardziej podstawowe pytanie: "Czy u偶yto jakich艣 rzutowa艅 w tym programie?" Dlatego z du偶ym prawdopodobie艅stwem cz艂owiek mo偶e podczas czytania programu nie zauwa偶y膰 zastosowanych w nim rzutowa艅, taki za艣 program jak grep nie potrafi ich odr贸偶ni膰 od sk艂adniowo podobnych konstrukcji, nie b臋d膮cych jednak rzutowaniami.
W odr贸偶nieniu od tego skr贸towego stylu zapisywania rzutowania w j臋zyku C, w j臋zyku C++ wprowadzono cztery nowe operatory rzutowania:
static_cast, const_cast, dynamic_cast oraz reinterpret_cast. W wi臋kszo艣ci sytuacji wszystko, co trzeba wiedzie膰 o tych operatorach, sprowadza si臋 do tego, 偶e je艣li do tej pory zwyk艂o si臋 pisa膰:
(typ) wyra偶enie
to teraz powinno si臋 napisa膰: static_cast<typ>(wyra偶enie)
Na przyk艂ad przypu艣膰my, 偶e chcieli艣my rzutowa膰 obiekt typu int do typu double, aby spowodowa膰, 偶e warto艣膰 wyra偶enia typu int uzyska posta膰 zmiennopozycyjn膮. Stosuj膮c rzutowanie w艂a艣ciwe j臋zykowi C mo偶na to tak zapisa膰:
int pierwszaLiczba, drugaLiczba;double wynik = ((double)pierwszaLiczba)/drugaLiczba;
U偶ywaj膮c nowych operator贸w rzutowania mo偶na to napisa膰 w ten spos贸b:
double wynik = static_cast<double>(pierwszaLiczba)/drugaLiczba;
Teraz jest to rzutowanie, kt贸re z 艂atwo艣ci膮 dostrzeg膮 ludzie, a i programy te偶 je rozpoznaj膮.Operator static_cast ma zasadniczo takie samo znaczenie i moc oddzia艂ywania jak zwyk艂y, wyst臋puj膮cy w j臋zyku C operator rzutowania. Podlega te偶 tego samego rodzaju ograniczeniom. Na przyk艂ad tak jak przy rzutowaniu w艂a艣ciwym dla j臋zyka C, r贸wnie偶 i za pomoc膮 operatora static_cast nie mo偶na rzutowa膰 obiektu typu struct do typu int ani obiektu typu double uczyni膰 wska藕nikiem. Co wi臋cej, operatora static_cast nie mo偶na u偶y膰 do spowodowania tego, by typ wyra偶enia pozbawi膰 modyfikatora const, poniewa偶 do tego celu przeznaczono w艂a艣nie drugi nowy operator rzutowania: const_cast.Pozosta艂e nowe operatory rzutowania w j臋zyku C++ maj膮 szczeg贸lne przeznaczenie. Operator const_cast s艂u偶y do pozbawienia wyra偶e艅 ich w艂a艣ciwo艣ci const albo volatile. U偶ywaj膮c tego operatora, zwracasz uwag臋 (ludzi i kompilator贸w) na fakt, 偶e jedyn膮 rzecz膮, jak膮 chcesz zmieni膰 za pomoc膮 rzutowania, jest w艂a艣ciwo艣膰 const b膮d藕 volatile. Kompilatory wymuszaj膮 takie znaczenie tego operatora. Je偶eli spr贸bujesz zastosowa膰 operator const_cast do jakich艣 innych cel贸w ni偶 pozbawienie jakiego艣 wyra偶enia w艂a艣ciwo艣ci const lub volatile, to twoje rzutowanie b臋dzie usuni臋te. Oto par臋 przyk艂ad贸w.
class Cokolwiek { ... };class SpecjalneCokolwiek: public Cokolwiek { ... };
void uaktualnij(SpecjalneCokolwiek *wsc); const SpecjalneCokolwiek sc;
uaktualnij(&sc); // b艂膮d, w ten spos贸b sta艂ego wska藕nika
// *SpecjalneCokolwiek nie mo偶na przekaza膰
// do funkcji pobieraj膮cej SpecjalneCokolwiek*
uaktualnij(const_cast<SpecjalneCokolwiek*>(&wsc); // dobrze, typ sta艂y argumentu sc jest // jawnie rzutowany (wi臋c sc mo偶e ulec // zmianie wewn膮trz funkcji uaktualnij) uaktualnij((SpecjalneCokolwiek*)&sc); // tak jak wy偶ej, ale przy u偶yciu
// trudniejszego do rozpoznania rzutowania // w stylu j臋zyka C
Cokolwiek *wc = new SpecjalneCokolwiek;
uaktualnij(wc); // b艂膮d, wc jest typu Cokolwiek*, ale
// uaktualnij pobiera SpecjalneCokolwiek* uaktualnij(const cast<SpecjalneCokolwiek*>(wc));
// b艂膮d, operatora const_cast mo偶na u偶y膰 tylko // w odniesieniu do typ贸w z modyfikatorem
// const lub volatile, ale nigdy do rzutowania // w obr臋bie hierarchii dziedziczenia
Najcz臋stszym zastosowaniem operatora const_cast jest usuwanie w艂a艣ciwo艣ci const obiektu.
Drugi szczeg贸lny rodzaj rzutowania, kt贸ry jest wyra偶any operatorem dynamic_cast, s艂u偶y do wykonywania bezpiecznego rzutowania w obr臋bie hierarchii dziedziczenia klas. Znaczy to, 偶e operatora dynamic_cast u偶ywamy po to, aby wska藕niki lub odniesienia do obiekt贸w klasy podstawowej zamieni膰 na wska藕niki lub odniesienia do obiekt贸w klas pochodnych lub pokrewnych, przy tym w taki spos贸b, kt贸ry pozwala okre艣li膰, czy rzutowanie si臋 powiod艂o. Niepowodzenie operacji rzutowania oznacza si臋 wska藕nikiem pustym (gdy rzutowano wska藕niki) lub sytuacj膮 wyj膮tkow膮 (gdy rzutowano odniesienia).
Cokolwiek *wc ;
...
uaktualnij(dynamic_cast<SpecjalneCokolwiek*>(wc));
// dobrze, do funkcji uaktualnij przekazuje wska藕nik wc do obiektu klasy
// SpecjalneCokolwiek wtedy, gdy wc rzeczywi艣cie na niego
// wskazuje, w przeciwnym razie przekazuje wska藕nik pusty
Drugie, nie zwi膮zane z tym zastosowanie operatora dynamic_cast polega na odnajdowaniu pocz膮tku obszaru pami臋ci zaj臋tego przez obiekt. Om贸wimy to zastosowanie w punkcie 27.
void uaktualnijPrzezOdniesienie(SpecjalneCokolwiek &osc);
uaktualnijPrzezOdniesienie( dynamic cast<SpecjalneCokolwiek&>(*wc));
// dobrze, do uaktualnijPrzezOdniesienie przekazuje wska藕nik wc do// obiektu klasy SpecjalneCokolwiek wtedy, gdy wc rzeczywi艣cie na niego// wskazuje, w przeciwnym razie zg艂asza sytuacja wyj膮tkow膮
Zastosowanie operatora dynamic_cast jest ograniczone do u艂atwiania poruszania si臋 po hierarchiach dziedziczenia klas. Nie mo偶na go u偶ywa膰 do typ贸w, kt贸re nie maj膮 funkcji wirtualnych (zob. te偶 punkt 24), ani te偶 w celu usuwania w艂a艣ciwo艣ci const:
int pierwszaLiczba, drugaLiczba;
...
double wynik = dynamic cast<double>(pierwszaLiczba)/drugaLiczba;
// b艂膮d, nie dotyczy dziedziczenia
const SpecjalneCokolwiek sc;
uaktualnij(dynamic cast<specjalneCokolwiek*>(&sc)); // b艂膮d, operator dynamic cast nie usuwa w艂a艣ciwo艣ci const
Je偶eli chcesz dokona膰 rzutowania do typu, kt贸ry nie podlega dziedziczeniu, to prawie na pewno chodzi Ci o zastosowanie operatora static_cast. Aby za艣 w wyniku rzutowania usun膮膰 w艂a艣ciwo艣膰 const, korzystaj zawsze z operatora const_cast.Ostatnim z czterech nowych operator贸w rzutowania jest operator reinterpret_cast. S艂u偶y on do wykonywania takich przekszta艂ce艅 typ贸w, kt贸rych wynik jest prawie zawsze zdefiniowany przez dan膮 implementacj臋 j臋zyka C++. Z tego powodu rzutowania zaprogramowane przy u偶yciu tego operatora rzadko bywaj膮 przeno艣ne.
Najcz臋stszym zastosowaniem operatora reinterpret_cast jest rzutowanie mi臋dzy typami wska藕nik贸w do funkcji. Na przyk艂ad przypu艣膰my, 偶e mamy tablic臋 wska藕nik贸w do funkcji konkretnego typu:
typedef void (*WskFun)(); // WskFun jest wska藕nikiem do funkcji, kt贸ra nie pobiera
// argument贸w i nie przekazuje warto艣ciWskFun tablWskFun[10];
// tablWskFun jest tablic膮 10 wska藕nik贸w WskFun
Przypu艣膰my teraz, 偶e chcemy (z jakich艣 niejasnych powod贸w) w tablicy tablWskFun umie艣ci膰 wska藕nik do poni偶szej funkcji:
int wykonajByleCo();
Nie mo偶na tego zrobi膰 bez rzutowania, poniewa偶 tak zadeklarowana funkcja wykonajByleCo ma inny typ ni偶 funkcje w tablicy tablWskFun. Funkcje te przekazuj膮 warto艣膰 pust膮 (s膮 typu void), funkcja wykonajByleCo przekazuje warto艣膰 typu int.
tablWskFun[0] = &wykonajByleCo; // b艂膮d, niezgodno艣膰 typ贸w
Operator reinterpret_cast pozwala nam zmusi膰 kompilatory, by posz艂y nam na r臋k臋:
tablWskFun[0] = // to b臋dzie przet艂umaczone reinterpret_cast<WskFun>(&wykonajByleCo);
Rzutowanie wska藕nik贸w do funkcji nie jest przeno艣ne (j臋zyk C++ nie daje 偶adnych gwarancji na to, 偶e wszystkie wska藕niki do funkcji b臋d膮 reprezentowane w taki sam spos贸b), a w pewnych przypadkach takie rzutowanie daje niepoprawne wyniki (zob. punkt 31). Dlatego nale偶y unika膰 rzutowania wska藕nik贸w do funkcji, chyba 偶e jeste艣 w sytuacji bez wyj艣cia - masz n贸偶 na gardle i to bardzo ostry n贸偶!
Je偶eli w Twoich kompilatorach nie ma wsparcia dla nowych operator贸w rzutowania, to zamiast operator贸w static_cast, const_cast lub reinterpret_cast mo偶esz u偶y膰 zwyk艂ego sposobu rzutowania. Co wi臋cej, mo偶esz zastosowa膰 makroinstrukcje, kt贸re pozwol膮 imitowa膰 u偶ycie nowych sposob贸w rzutowania:
#define static_cast(TYP,WYR) (TYP)(WYR)
#define const_cast(TYP,WYR) (TYP)(WYR) #define reinterpret_cast(TYP,WYR) (TYP)(WYR)
Mo偶esz z nich korzysta膰 w nast臋puj膮cy spos贸b:
double wynik = static_cast(double, pierwszaLiczba) / drugaLiczba;
uaktualnij(const_cast(SpecjalneCokolwiek*, &sc));
tablWskFun[0] = reinterpret_cast(WskFun, &wykonajByleCo);
Oczywi艣cie takie zabiegi nie b臋d膮 tak bezpieczne, jak zastosowanie w艂a艣ciwych operator贸w, ale uproszcz膮 Ci tworzenie nowych wersji program贸w wtedy, kiedy ju偶 b臋dziesz dysponowa膰 kompilatorami, kt贸re maj膮 wsparcie dla nowych operator贸w rzutowania.
Nie ma te偶 艂atwego sposobu na艣ladowania zachowania operatora dynamic_cast, ale w wielu bibliotekach istniej膮 funkcje, kt贸re umo偶liwiaj膮 wykonanie bezpiecznych operacji rzutowania do klas pochodnych. Je偶eli nie masz takich funkcji, ale musisz wykona膰 ten rodzaj rzutowania, to mo偶esz w tym celu powr贸ci膰 do sposobu rzutowania w艂a艣ciwego j臋zykowi C, ale wtedy nale偶y po偶egna膰 si臋 z mo偶liwo艣ci膮 informowania o niepowodzeniu tej operacji. Nie trzeba dodawa膰, 偶e podobnie jak w powy偶szych przyk艂adach - mo偶esz zdefiniowa膰 makroinstrukcj臋, kt贸ra b臋dzie wygl膮da膰 tak jak dynamic_cast:#define dynamic_cast(TYP,WYR) (TYP)(WYR)Pami臋taj jednak, 偶e nie powoduje to wykonania prawdziwej operacji dynamic_cast; nie ma sposobu stwierdzenia, 偶e rzutowanie si臋 nie powiod艂o.
Wiem, oczywi艣cie wiem, 偶e nowe operatory rzutowania s膮 brzydkie i niewygodne. Je艣li ich wygl膮d wydaje Ci si臋 zbyt nieprzyjemny, to pociesz si臋 wiadomo艣ci膮, 偶e rzutowanie takie jak w j臋zyku C jest nadal poprawne. Jednak to, co nowe operatory rzutowania straci艂y na urodzie, nadrabiaj膮 dzi臋ki dok艂adnemu okre艣leniu ich znaczenia i 艂atwo艣ci ich rozpoznawania. 艁atwiejsze jest (zar贸wno dla ludzi, jak i dla narz臋dzi) analizowanie program贸w, w kt贸rych u偶yto nowych operator贸w rzutowania; ponadto kompilatory mog膮 w takich programach wyszukiwa膰 b艂臋dy, kt贸re przy dawnym zapisie rzutowania pozosta艂yby nie zauwa偶one. S膮 to powa偶ne argumenty przemawiaj膮ce za wycofaniem si臋 ze stosowania rzutowania w stylu j臋zyka C, a mo偶na doda膰 do nich jeszcze jeden: ot贸偶, by膰 mo偶e, dobrze si臋 sta艂o, 偶e operatory rzutowania wygl膮daj膮 brzydko i niezbyt wygodnie pisze si臋 je w programie.
Wyszukiwarka
Podobne podstrony:
punkt06punkt01punkt08punkt04punkt09wi臋cej podobnych podstron