punkt06






Scott Meyers "J臋zyk C++ bardziej efektywny", WNT, 1998




Scott Meyers 揓臋zyk C++ bardziej efektywny", WNT, 1998
 
 
Punkt 6: Rozr贸偶niaj przedrostkowe i przyrostkowe postaci operator贸w zwi臋kszania i zmniejszania
 
Dawno, dawno temu (w p贸藕nych latach osiemdziesi膮tych) w j臋zyku, o kt贸rym ju偶 nikt nie pami臋ta (w 贸wczesnym C++) nie mo偶na by艂o odr贸偶ni膰 przedrostkowych od przyrostkowych wywo艂a艅 operator贸w ++ oraz --. Programi艣ci narzekali na to zaniedbanie, wi臋c rozszerzono j臋zyk C++, zezwalaj膮c na przeci膮偶anie obu postaci operator贸w zwi臋kszania i zmniejszania.
Powsta艂 jednak problem sk艂adniowy, poniewa偶 funkcje przeci膮偶ane s膮 rozr贸偶niane na podstawie typu pobieranego przez nie argumentu, gdy tymczasem ani przedrostkowe, ani przyrostkowe operatory zwi臋kszania lub zmniejszania nie pobieraj膮 argument贸w. Aby przebrn膮膰 przez te lingwistyczne wyboje postanowiono, 偶e operatory te wyst臋puj膮ce w postaci przyrostkowej pobieraj膮 argument typu int, kiedy za艣 ich funkcje s膮 wywo艂ywane, wtedy kompilatory milcz膮co przekazuj膮 0 jako argument typu int.
 
class CND // typ liczb ca艂kowitych o nieograniczonej dok艂adno艣ci
{ public:
CND& operator++(); // przedrostkowy ++
const CND operator++(int); // przyrostkowy ++
CND& operator(); // przedrostkowy

const CND operator(int); // przyrostkowy

CND& operator+=(int); // operator += dla typ贸w CND i int
// ...
};
CND i;
++i; // wywo艂uje i.operator++();
i++; // wywo艂uje i.operator++(0);
--i; // wywo艂uje i.operator();
i--; // wywo艂uje i.operator(0);
 
Ta konwersja jest troch臋 dziwaczna, ale mo偶na si臋 do niej przyzwyczai膰. Wa偶niejsz膮 jednak spraw膮 jest przyzwyczajenie si臋 do tego, 偶e przedrostkowe i przyrostkowe postaci tych operator贸w przekazuj膮 r贸偶ne typy. M贸wi膮c 艣ci艣le, postaci przedrostkowe przekazuj膮 odniesienie, przyrostkowe za艣
obiekt z modyfikatorem const. Zajmiemy si臋 poni偶ej postaciami operatora ++, lecz to samo dotyczy obydwu postaci operatora --.
Ci programi艣ci, kt贸rzy u偶ywali kiedy艣 j臋zyka C, mog膮 pami臋ta膰, 偶e na przedrostkow膮 posta膰 operatora zwi臋kszania m贸wiono czasami 搝wi臋ksz i pobierz", podczas gdy jego posta膰 przyrostkow膮 zwano 損obierz i zwi臋ksz". Te dwa zwroty warto pami臋ta膰, poniewa偶 stanowi膮 niemal formaln膮 specyfikacj臋 tego, jak powinno si臋 implementowa膰 przedrostkowy operator zwi臋kszania:
 
// posta膰 przedrostkowa: zwi臋ksz i pobierz
CND& CND::operator++()
{ *this += 1; // zwi臋ksz
return *this; // pobierz
}
// posta膰 przyrostkowa: pobierz i zwi臋ksz
const CND CND::operator++(int)
{ CND staraWarto艣膰 = *this; // pobierz
++(*this); // zwi臋ksz
return staraWarto艣膰 // przeka偶 to, co by艂o pobrane
}
Zauwa偶my, 偶e operator w postaci przyrostkowej w og贸le nie korzysta ze swego argumentu. Jest to zachowanie typowe dla tej postaci operatora. Argument s艂u偶y jedynie do odr贸偶nienia postaci przyrostkowej od przedrostkowej w wywo艂aniu funkcji. Wiele kompilator贸w wysy艂a ostrze偶enie wtedy, kiedy kto艣 zaniedba u偶ycia nazwanych argument贸w w tre艣ci funkcji, kt贸rej dotycz膮: mo偶e to okaza膰 si臋 dokuczliwe. Aby unikn膮膰 takich ostrze偶e艅, stosuje si臋 popularn膮 strategi臋: opuszcza si臋 nazwy tych argument贸w, kt贸rych nie zamierzamy u偶y膰: tak w艂a艣nie post膮piono powy偶ej.
Jest spraw膮 oczywist膮, dlaczego przyrostkowy operator zwi臋kszania musi przekaza膰 pewien obiekt (swoj膮 poprzedni膮 warto艣膰), ale dlaczego typ tego obiektu musi mie膰 modyfikator const? Wyobra藕my sobie, 偶e nie ma tego modyfikatora. W贸wczas poprawne b臋d膮 nast臋puj膮ce instrukcje:
CND i;
i++++; // dwukrotnie u偶yj przyrostkowego operatora zwi臋kszania
Jest to r贸wnowa偶ne instrukcji:
i.operator++(0).operator++(0);
przy czym powinno by膰 jasne, 偶e drugie wywo艂anie funkcji operator++ zastosowano do obiektu przekazanego przez pierwsze jej wywo艂anie.
 
Nie mo偶na si臋 z tym pogodzi膰 z dw贸ch powod贸w. Po pierwsze jest to niezgodne z zachowaniem si臋 typ贸w wbudowanych do j臋zyka. Nale偶y przyj膮膰 dobr膮 zasad臋, 偶e kiedy okre艣lenie klasy wzbudza w膮tpliwo艣ci, wtedy trzeba tak post臋powa膰, jak w przypadku typu int, niew膮tpliwie za艣 obiekty tego typu nie zezwalaj膮 na dwukrotne zastosowanie przyrostkowej postaci operatora zwi臋kszania:
int i;
i++++; // b艂膮d
Drugim powodem jest fakt, 偶e dwukrotne u偶ycie przyrostkowego operatora zwi臋kszania prawie nigdy nie daje tego, czego chcieliby艣my oczekiwa膰. Zauwa偶yli艣my powy偶ej, 偶e drugie zastosowanie funkcji operator++ w dwukrotnym jej wywo艂aniu zmienia warto艣膰 obiektu przekazanego przez pierwsze wywo艂anie, nie za艣 warto艣膰 pierwotnego obiektu. Tak wi臋c, je偶eli instrukcja
i++++;
by艂aby poprawna, to warto艣膰 zmiennej i zosta艂aby zwi臋kszona tylko raz. Jest to niezgodne z intuicj膮, a przy tym myl膮ce (zar贸wno w przypadku typu int, jak klasy CND), zatem najlepiej tego zabroni膰.
J臋zyk C++ zabrania takiego post臋powania w odniesieniu do obiekt贸w typu int, ale ka偶dy programista, kt贸ry tworzy klasy, musi sam sobie tego zakaza膰. Naj艂atwiej tego dokona膰 ustalaj膮c, 偶e typ obiektu przekazywanego przez przyrostkow膮 operacj臋 zwi臋kszania ma modyfikator const. Tak wi臋c, je偶eli kompilatory przeczytaj膮 instrukcj臋
i++++; // r贸wnowa偶ne instrukcji i.operator++(0).operator++(0);
to rozpoznaj膮, 偶e obiekt sta艂y, przekazany przez pierwsze wywo艂anie funkcji operator++ ma by膰 u偶yty przez ponowne wywo艂anie tej samej funkcji. Jednak偶e operator++ nie jest zadeklarowany z modyfikatorem const, wi臋c obiekt sta艂y, taki jaki jest przekazywany przez funkcj臋 zwi臋kszania przyrostkowego operator++ nie mo偶e wywo艂a膰 tego operatora. Ka偶dy, kto kiedykolwiek zastanawia艂 si臋, czy przekazywanie obiektu sta艂ego przez funkcj臋 ma jaki艣 sens, wie teraz, 偶e bywa tak czasami, a jako przyk艂ady mo偶na przytoczy膰 przyrostkowe operatory zwi臋kszania i zmniejszania.
Zapewne ka偶dy, kto troszczy si臋 o wydajno艣膰 program贸w, zmartwi艂 si臋, widz膮c po raz pierwszy funkcj臋 zwi臋kszania w postaci przyrostkowej. Funkcja ta musi utworzy膰 obiekt tymczasowy dla swojej warto艣ci przekazywanej, a powy偶sza implementacja r贸wnie偶 powoduje, 偶e trzeba utworzy膰 jawny obiekt tymczasowy (staraWarto艣膰), kt贸ry nast臋pnie trzeba usun膮膰. Funkcja zwi臋kszania w postaci przedrostkowej nie ma takich obiekt贸w tymczasowych. Prowadzi to do zdumiewaj膮cych wniosk贸w, 偶e kieruj膮c si臋 jedynie dba艂o艣ci膮 o nale偶yt膮 wydajno艣膰 w przypadku korzystania z klasy CND, powinno si臋 raczej u偶ywa膰 przedrostkowego operatora zwi臋kszania, chyba 偶e rzeczywi艣cie potrzebny nam efekt zastosowania przyrostkowego operatora zwi臋kszania. Powiedzmy to wyra藕nie. Kiedy dzia艂amy na obiektach typ贸w zdefiniowanych przez u偶ytkownika, wtedy zawsze, je艣li tylko to mo偶liwe, powinno si臋 u偶ywa膰 przedrostkowego operatora zwi臋kszania, poniewa偶 jest on zdecydowanie wydajniejszy.
Zwr贸膰my uwag臋 na jeszcze jedn膮 w艂a艣ciwo艣膰 tych dw贸ch postaci operatora zwi臋kszania. Je艣li pominiemy ich warto艣ci przekazywane, to wida膰, 偶e robi膮 one to samo: zwi臋kszaj膮 pewn膮 warto艣膰. To znaczy
przypuszczalnie robi膮 to samo. Jak膮 mamy gwarancj臋, 偶e po jakim艣 czasie ich implementacje nie b臋d膮 odmienne, by膰 mo偶e w wyniku zabieg贸w r贸偶nych programist贸w? Je偶eli nie b臋dziemy post臋powa膰 zgodnie z zasad膮 zawart膮 w powy偶ej przytoczonym przyk艂adzie, to nie mamy takiej gwarancji. Zasada ta g艂osi, 偶e przyrostkowe operatory zwi臋kszania i zmniejszania powinny by膰 implementowane za po艣rednictwem ich przedrostkowych odpowiednik贸w. Trzeba zatem utrzymywa膰 tylko wersje przedrostkowe tych operator贸w, poniewa偶 wersje przyrostkowe automatycznie zachowaj膮 si臋 zgodnie z tamtymi.
[...]



Wyszukiwarka

Podobne podstrony:
punkt02
punkt01
punkt08
punkt04
punkt09

wi臋cej podobnych podstron