current = new Link<K,V>(k,def_val); current->pre = p->pre; current->suc = p; if (p == head) // tekushchij element stanovitsya nachal'nym head = current; else p->pre->suc = current; p->pre = current; return current->value; } Link<K,V>* s = p->suc; if (s == 0) { // vstavit' posle p (v konec) current = new Link<K,V>(k,def_val); current->pre = p; current->suc = 0; p->suc = current; return current->value; } p = s; } } Operaciya indeksacii vozvrashchaet ssylku na znachenie, kotoroe sootvetstvuet zadannomu kak parametr klyuchu. Esli takoe znachenie ne najdeno, vozvrashchaetsya novyj element so standartnym znacheniem. |to pozvolyaet ispol'zovat' operaciyu indeksacii v levoj chasti prisvaivaniya. Standartnye znacheniya dlya klyuchej i znachenij ustanavlivayutsya konstruktorami Map. V operacii indeksacii opredelyaetsya znachenie current, ispol'zuemoe iteratorami. Realizaciya ostal'nyh funkcij-chlenov ostavlena v kachestve uprazhneniya: template<class K, class V> void Map<K,V>::remove(const K& k) { // sm. uprazhnenie 2 iz $$8.10 } template<class K, class V> Map<K,V>::Map(const Map<K,V>& m) { // kopirovanie tablicy Map i vseh ee elementov } template<class K, class V> Map& Map<K,V>::operator=(const Map<K,V>& m) { // kopirovanie tablicy Map i vseh ee elementov } Teper' nam ostalos' tol'ko opredelit' iteraciyu. V klasse Map est' funkcii-chleny first(), last() i element(const K&), kotorye vozvrashchayut iterator, ustanovlennyj sootvetstvenno na pervyj, poslednij ili zadavaemyj klyuchom-parametrom element. Sdelat' eto mozhno, poskol'ku elementy hranyatsya v uporyadochennom po klyucham vide. Iterator Mapiter dlya Map opredelyaetsya tak: template<class K, class V> class Mapiter { friend class Map<K,V>; Map<K,V>* m; Link<K,V>* p; Mapiter(Map<K,V>* mm, Link<K,V>* pp) { m = mm; p = pp; } public: Mapiter() { m = 0; p = 0; } Mapiter(Map<K,V>& mm); operator void*() { return p; } const K& key(); V& value(); Mapiter& operator--(); // prefiksnaya void operator--(int); // postfiksnaya Mapiter& operator++(); // prefiksnaya void operator++(int); // postfiksnaya }; Posle pozicionirovaniya iteratora funkcii key() i value() iz Mapiter vydayut klyuch i znachenie togo elementa, na kotoryj ustanovlen iterator. template<class K, class V> const K& Mapiter<K,V>::key() { if (p) return p->key; else return m->def_key; } template<class K, class V> V& Mapiter<K,V>::value() { if (p) return p->value; else return m->def_val; } Po analogii s ukazatelyami opredeleny operacii ++ i -- dlya prodvizheniya po elementam Map vpered i nazad: Mapiter<K,V>& Mapiter<K,V>::operator--() //prefiksnyj dekrement { if (p) p = p->pre; return *this; } void Mapiter<K,V>::operator--(int) // postfiksnyj dekrement { if (p) p = p->pre; } Mapiter<K,V>& Mapiter<K,V>::operator++() // prefiksnyj inkrement { if (p) p = p->suc; return *this; } void Mapiter<K,V>::operator++(int) // postfiksnyj inkrement { if (p) p = p->suc; } Postfiksnye operacii opredeleny tak, chto oni ne vozvrashchayut nikakogo znacheniya. Delo v tom, chto zatraty na sozdanie i peredachu novogo ob容kta Mapiter na kazhdom shage iteracii znachitel'ny, a pol'za ot nego budet ne velika. Ob容kt Mapiter mozhno inicializirovat' tak, chtoby on byl ustanovlen na nachalo Map: template<class K, class V> Mapiter<K,V>::Mapiter(Map<K,V>& mm) { m == &mm; p = m->head; } Operaciya preobrazovaniya operator void*() vozvrashchaet nul', esli iterator ne ustanovlen na element Map, i nenulevoe znachenie inache. Znachit mozhno proveryat' iterator iter, naprimer, tak: void f(Mapiter<const char*, Shape*>& iter) { // ... if (iter) { // ustanovlen na element tablicy } else { // ne ustanovlen na element tablicy } // ... } Analogichnyj priem ispol'zuetsya dlya kontrolya potokovyh operacij vvoda-vyvoda v $$10.3.2. Esli iterator ne ustanovlen na element tablicy, ego funkcii key() i value() vozvrashchayut ssylki na standartnye ob容kty. Esli posle vseh etih opredelenij vy zabyli ih naznachenie, mozhno privesti eshche odnu nebol'shuyu programmu, ispol'zuyushchuyu tablicu Map. Pust' vhodnoj potok yavlyaetsya spiskom par znachenij sleduyushchego vida: hammer 2 nail 100 saw 3 saw 4 hammer 7 nail 1000 nail 250 Nuzhno otsortirovat' spisok tak, chtoby znacheniya, sootvetstvuyushchie odnomu predmetu, skladyvalis', i napechatat' poluchivshijsya spisok vmeste s itogovym znacheniem: hammer 9 nail 1350 saw 7 ------------------- total 1366 Vnachale napishem funkciyu, kotoraya chitaet vhodnye stroki i zanosit predmety s ih kolichestvom v tablicu. Klyuchom v etoj tablice yavlyaetsya pervoe slovo stroki: template<class K, class V> void readlines(Map<K,V>&key) { K word; while (cin >> word) { V val = 0; if (cin >> val) key[word] +=val; else return; } } Teper' mozhno napisat' prostuyu programmu, vyzyvayushchuyu funkciyu readlines() i pechatayushchuyu poluchivshuyusya tablicu: main() { Map<String,int> tbl("nil",0); readlines(tbl); int total = 0; for (Mapiter<String,int> p(tbl); p; ++p) { int val = p.value(); total +=val; cout << p.key() << '\t' << val << '\n'; } cout << "--------------------\n"; cout << "total\t" << total << '\n'; } 8.9 Uprazhneniya 1. (*2) Opredelite semejstvo spiskov s dvojnoj svyaz'yu, kotorye budut dvojnikami spiskov s odnoj svyaz'yu, opredelennyh v $$8.3. 2. (*3) Opredelite shablon tipa String, parametrom kotorogo yavlyaetsya tip simvola. Pokazhite kak ego mozhno ispol'zovat' ne tol'ko dlya obychnyh simvolov, no i dlya gipoteticheskogo klassa lchar, kotoryj predstavlyaet simvoly ne iz anglijskogo alfavita ili rasshirennyj nabor simvolov. Nuzhno postarat'sya tak opredelit' String, chtoby pol'zovatel' ne zametil uhudsheniya harakteristik programmy po pamyati i vremeni ili v udobstve po sravneniyu s obychnym strokovym klassom. 3. (*1.5) Opredelite klass Record (zapis') s dvumya chlenami-dannymi: count (kolichestvo) i price (cena). Uporyadochite vektor iz takih zapisej po kazhdomu iz chlenov. Pri etom nel'zya izmenyat' funkciyu sortirovki i shablon Vector. 4. (*2) Zavershite opredeleniya shablonnogo klassa Map, napisav nedostayushchie funkcii-chleny. 5. (*2) Zadajte druguyu realizaciyu Map iz $$8.8, ispol'zuya spisochnyj klass s dvojnoj svyaz'yu. 6. (*2.5) Zadajte druguyu realizaciyu Map iz $$8.8, ispol'zuya sbalansirovannoe derevo. Takie derev'ya opisany v $$6.2.3 knigi D. Knut "Iskusstvo programmirovaniya dlya |VM" t.1, "Mir", 1978 [K]. 7. (*2) Sravnite kachestvo dvuh realizacij Map. V pervoj ispol'zuetsya klass Link so svoej sobstvennoj funkciej razmeshcheniya, a vo vtoroj - bez nee. 8. (*3) Sravnite proizvoditel'nost' programmy podscheta slov iz $$8.8 i takoj zhe programmy, ne ispol'zuyushchej klassa Map. Operacii vvoda-vyvoda dolzhny odinakovo ispol'zovat'sya v obeih programmah. Sravnite neskol'ko takih programm, ispol'zuyushchih raznye varianty klassa Map, v tom chisle i klass iz vashej biblioteki, esli on tam est'. 9. (*2.5) S pomoshch'yu klassa Map realizujte topologicheskuyu sortirovku. Ona opisana v [K] t.1, str. 323-332. (sm. uprazhnenie 6). 10. (*2) Modificirujte programmu iz $$8.8 tak, chtoby ona rabotala pravil'no dlya dlinnyh imen i dlya imen, soderzhashchih probely (naprimer, "thumb back"). 11. (*2) Opredelite shablon tipa dlya chteniya razlichnyh vidov strok, naprimer, takih (predmet, kolichestvo, cena). 12. (*2) Opredelite klass Sort iz $$8.4.5, ispol'zuyushchij sortirovku po metodu SHella. Pokazhite kak mozhno zadat' metod sortirovki s pomoshch'yu parametra shablona. Algoritm sortirovki opisan v [K] t.3, $$5.2.1 (sm. uprazhnenie 6). 13. (*1) Izmenite opredeleniya Map i Mapiter tak, chtoby postfiksnye operacii ++ i -- vozvrashchali ob容kt Mapiter. 14. (*1.5) Ispol'zujte shablony tipa v stile modul'nogo programmirovaniya, kak eto bylo pokazano v $$8.4.5 i napishite funkciyu sortirovki, rasschitannuyu srazu na Vector<T> i T[].  * GLAVA 9 YA prerval vas, poetomu ne preryvajte menya. - Uinston CHerchill V etoj glave opisan mehanizm obrabotki osobyh situacij i nekotorye, osnovyvayushchiesya na nem, sposoby obrabotki oshibok. Mehanizm sostoit v zapuske osoboj situacii, kotoruyu dolzhen perehvatit' special'nyj obrabotchik. Opisyvayutsya pravila perehvata osobyh situacij i pravila reakcii na neperehvachennye i neozhidannye osobye situacii. Celye gruppy osobyh situacij mozhno opredelit' kak proizvodnye klassy. Opisyvaetsya sposob, ispol'zuyushchij destruktory i obrabotku osobyh situacij, kotoryj obespechivaet nadezhnoe i skrytoe ot pol'zovatelya upravlenie resursami. 9.1 Obrabotka oshibok Sozdatel' biblioteki sposoben obnaruzhit' dinamicheskie oshibki, no ne predstavlyaet kakoj v obshchem sluchae dolzhna byt' reakciya na nih. Pol'zovatel' biblioteki sposoben napisat' reakciyu na takie oshibki, no ne v silah ih obnaruzhit'. Esli by on mog, to sam razobralsya by s oshibkami v svoej programme, i ih ne prishlos' by vyyavlyat' v bibliotechnyh funkciyah. Dlya resheniya etoj problemy v yazyk vvedeno ponyatie osoboj situacii X. X Tol'ko nedavno komitetom po standartizacii S++ osobye situacii byli vklyucheny v standart yazyka, no na vremya napisaniya etoj knigi oni eshche ne voshli v bol'shinstvo realizacij. Sut' etogo ponyatiya v tom, chto funkciya, kotoraya obnaruzhila oshibku i ne mozhet spravit'sya s neyu, zapuskaet osobuyu situaciyu, rasschityvaya, chto ustranit' problemu mozhno v toj funkcii, kotoraya pryamo ili oposredovanno vyzyvala pervuyu. Esli funkciya rasschitana na obrabotku oshibok nekotorogo vida, ona mozhet ukazat' eto yavno, kak gotovnost' perehvatit' dannuyu osobuyu situaciyu. Rassmotrim v kachestve primera kak dlya klassa Vector mozhno predstavlyat' i obrabatyvat' osobye situacii, vyzvannye vyhodom za granicu massiva: class Vector { int* p; int sz; public: class Range { }; // klass dlya osoboj situacii int& operator[](int i); // ... }; Predpolagaetsya, chto ob容kty klassa Range budut ispol'zovat'sya kak osobye situacii, i zapuskat' ih mozhno tak: int& Vector::operator[](int i) { if (0<=i && i<sz) return p[i]; throw Range(); } Esli v funkcii predusmotrena reakciya na oshibku nedopustimogo znacheniya indeksa, to tu chast' funkcii, v kotoroj eti oshibki budut perehvatyvat'sya, nado pomestit' v operator try. V nem dolzhen byt' i obrabotchik osoboj situacii: void f(Vector& v) { // ... try { do_something(v); // soderzhatel'naya chast', rabotayushchaya s v } catch (Vector::Range) { // obrabotchik osoboj situacii Vector::Range // esli do_something() zavershitsya neudachno, // nuzhno kak-to sreagirovat' na eto // syuda my popadem tol'ko v tom sluchae, kogda // vyzov do_something() privedet k vyzovu Vector::operator[]() // iz-za nedopustimogo znacheniya indeksa } // ... } Obrabotchikom osoboj situacii nazyvaetsya konstrukciya catch ( /* ... */ ) { // ... } Ee mozhno ispol'zovat' tol'ko srazu posle bloka, nachinayushchegosya sluzhebnym slovom try, ili srazu posle drugogo obrabotchika osoboj situacii. Sluzhebnym yavlyaetsya i slovo catch. Posle nego idet v skobkah opisanie, kotoroe ispol'zuetsya analogichno opisaniyu formal'nyh parametrov funkcii, a imenno, v nem zadaetsya tip ob容ktov, na kotorye rasschitan obrabotchik, i, vozmozhno, imena parametrov (sm. $$9.3). Esli v do_something() ili v lyuboj vyzvannoj iz nee funkcii proizojdet oshibka indeksa (na lyubom ob容kte Vector), to obrabotchik perehvatit osobuyu situaciyu i budet vypolnyat'sya chast', obrabatyvayushchaya oshibku. Naprimer, opredeleniya sleduyushchih funkcij privedut k zapusku obrabotchika v f(): void do_something() { // ... crash(v); // ... } void crash(Vector& v) { v[v.size()+10]; // iskusstvenno vyzyvaem oshibku indeksa } Process zapuska i perehvata osoboj situacii predpolagaet prosmotr cepochki vyzovov ot tochki zapuska osoboj situacii do funkcii, v kotoroj ona perehvatyvaetsya. Pri etom vosstanavlivaetsya sostoyanie steka, sootvetstvuyushchee funkcii, perehvativshej oshibku, i pri prohode po vsej cepochke vyzovov dlya lokal'nyh ob容ktov funkcij iz etoj cepochki vyzyvayutsya destruktory. Podrobno eto opisano v $$9.4. Esli pri prosmotre vsej cepochki vyzovov, nachinaya s zapustivshej osobuyu situaciyu funkcii, ne obnaruzhitsya podhodyashchij obrabotchik, to programma zavershaetsya. Podrobno eto opisano v $$9.7. Esli obrabotchik perehvatil osobuyu situaciyu, to ona budet obrabatyvat'sya i drugie, rasschitannye na etu situaciyu, obrabotchiki ne budut rassmatrivat'sya. Inymi slovami, aktivirovan budet tol'ko tot obrabotchik, kotoryj nahoditsya v samoj poslednej vyzyvavshejsya funkcii, soderzhashchej sootvetstvuyushchie obrabotchiki. V nashem primere funkciya f() perehvatit Vector::Range, poetomu etu osobuyu situaciyu nel'zya perehvatit' ni v kakoj vyzyvayushchej f() funkcii: int ff(Vector& v) { try { f(v); // v f() budet perehvachena Vector::Range } catch (Vector::Range) { // znachit syuda my nikogda ne popadem // ... } } 9.1.1 Osobye situacii i tradicionnaya obrabotka oshibok Nash sposob obrabotki oshibok po mnogim parametram vygodno otlichaetsya ot bolee tradicionnyh sposobov. Perechislim, chto mozhet sdelat' operaciya indeksacii Vector::operator[]() pri obnaruzhenii nedopustimogo znacheniya indeksa: [1] zavershit' programmu; [2] vozvratit' znachenie, traktuemoe kak "oshibka"; [3] vozvratit' normal'noe znachenie i ostavit' programmu v neopredelennom sostoyanii; [4] vyzvat' funkciyu, zadannuyu dlya reakcii na takuyu oshibku. Variant [1] ("zavershit' programmu") realizuetsya po umolchaniyu v tom sluchae, kogda osobaya situaciya ne byla perehvachena. Dlya bol'shinstva oshibok mozhno i nuzhno obespechit' luchshuyu reakciyu. Variant [2] ("vozvratit' znachenie "oshibka"") mozhno realizovat' ne vsegda, poskol'ku ne vsegda udaetsya opredelit' znachenie "oshibka". Tak, v nashem primere lyuboe celoe yavlyaetsya dopustimym znacheniem dlya rezul'tata operacii indeksacii. Esli mozhno vydelit' takoe osoboe znachenie, to chasto etot variant vse ravno okazyvaetsya neudobnym, poskol'ku proveryat' na eto znachenie prihoditsya pri kazhdom vyzove. Tak mozhno legko udvoit' razmer programmy. Poetomu dlya obnaruzheniya vseh oshibok etot variant redko ispol'zuetsya posledovatel'no. Variant [3] ("ostavit' programmu v neopredelennom sostoyanii") imeet tot nedostatok, chto vyzyvavshaya funkciya mozhet ne zametit' nenormal'nogo sostoyaniya programmy. Naprimer, vo mnogih funkciyah standartnoj biblioteki S dlya signalizacii ob oshibke ustanavlivaetsya sootvetstvuyushchee znachenie global'noj peremennoj errno. Odnako, v programmah pol'zovatelya obychno net dostatochno posledovatel'nogo kontrolya errno, i v rezul'tate voznikayut navedennye oshibki, vyzvannye tem, chto standartnye funkcii vozvrashchayut ne to znachenie. Krome togo, esli v programme est' parallel'nye vychisleniya, ispol'zovanie odnoj global'noj peremennoj dlya signalizacii o raznyh oshibkah neizbezhno privedet k katastrofe. Obrabotka osobyh situacij ne prednaznachalas' dlya teh sluchaev, na kotorye rasschitan variant [4] ( "vyzvat' funkciyu reakcii na oshibku"). Otmetim, odnako, chto esli osobye situacii ne predusmotreny, to vmesto funkcii reakcii na oshibku mozhno kak raz ispol'zovat' tol'ko odin iz treh perechislennyh variantov. Obsuzhdenie funkcij reakcij i osobyh situaciej budet prodolzheno v $$9.4.3. Mehanizm osobyh situacij uspeshno zamenyaet tradicionnye sposoby obrabotki oshibok v teh sluchayah, kogda poslednie yavlyayutsya nepolnym, nekrasivym ili chrevatym oshibkami resheniem. |tot mehanizm pozvolyaet yavno otdelit' chast' programmy, v kotoroj obrabatyvayutsya oshibki, ot ostal'noj ee chasti, tem samym programma stanovitsya bolee ponyatnoj i s nej proshche rabotat' razlichnym servisnym programmam. Svojstvennyj etomu mehanizmu regulyarnyj sposob obrabotki oshibok uproshchaet vzaimodejstvie mezhdu razdel'no napisannymi chastyami programmy. V etom sposobe obrabotki oshibok est' dlya programmiruyushchih na S novyj moment: standartnaya reakciya na oshibku (osobenno na oshibku v bibliotechnoj funkcii) sostoit v zavershenii programmy. Tradicionnoj byla reakciya prodolzhat' programmu v nadezhde, chto ona kak-to zavershitsya sama. Poetomu sposob, baziruyushchijsya na osobyh situaciyah, delaet programmu bolee "hrupkoj" v tom smysle, chto trebuetsya bol'she usilij i vnimaniya dlya ee normal'nogo vypolneniya. No eto vse-taki luchshe, chem poluchat' nevernye rezul'taty na bolee pozdnej stadii razvitiya programmy (ili poluchat' ih eshche pozzhe, kogda programmu sochtut zavershennoj i peredadut nichego ne podozrevayushchemu pol'zovatelyu). Esli zavershenie programmy yavlyaetsya nepriemlemoj reakciej, mozhno smodelirovat' tradicionnuyu reakciyu s pomoshch'yu perehvata vseh osobyh situacij ili vseh osobyh situacij, prinadlezhashchih special'nomu klassu ($$9.3.2). Mehanizm osobyh situacij mozhno rassmatrivat' kak dinamicheskij analog mehanizma kontrolya tipov i proverki neodnoznachnosti na stadii translyacii. Pri takom podhode bolee vazhnoj stanovitsya stadiya proektirovaniya programmy, i trebuetsya bol'shaya podderzhka processa vypolneniya programmy, chem dlya programm na S. Odnako, v rezul'tate poluchitsya bolee predskazuemaya programma, ee budet proshche vstroit' v programmnuyu sistemu, ona budet ponyatnee drugim programmistam i s nej proshche budet rabotat' razlichnym servisnym programmam. Mozhno skazat', chto mehanizm osobyh situacij podderzhivaet, podobno drugim sredstvam S++, "horoshij" stil' programmirovaniya, kotoryj v takih yazykah, kak S, mozhno primenyat' tol'ko ne v polnom ob容me i na neformal'nom urovne. Vse zhe nado soznavat', chto obrabotka oshibok ostaetsya trudnoj zadachej, i, hotya mehanizm osobyh situacij bolee strogij, chem tradicionnye sposoby, on vse ravno nedostatochno strukturirovan po sravneniyu s konstrukciyami, dopuskayushchimi tol'ko lokal'nuyu peredachu upravleniya. 9.1.2 Drugie tochki zreniya na osobye situacii "Osobaya situaciya" - odno iz teh ponyatij, kotorye imeyut raznyj smysl dlya raznyh lyudej. V S++ mehanizm osobyh situacij prednaznachen dlya obrabotki oshibok. V chastnosti, on prednaznachen dlya obrabotki oshibok v programmah, sostoyashchih iz nezavisimo sozdavaemyh komponentov. |tot mehanizm rasschitan na osobye situacii, voznikayushchie tol'ko pri posledovatel'nom vypolnenii programmy (naprimer, kontrol' granic massiva). Asinhronnye osobye situacii takie, naprimer, kak preryvaniya ot klaviatury, nel'zya neposredstvenno obrabatyvat' s pomoshch'yu etogo mehanizma. V razlichnyh sistemah sushchestvuyut drugie mehanizmy, naprimer, signaly, no oni zdes' ne rassmatrivayutsya, poskol'ku zavisyat ot konkretnoj sistemy. Mehanizm osobyh situacij yavlyaetsya konstrukciej s nelokal'noj peredachej upravleniya i ego mozhno rassmatrivat' kak variant operatora return. Poetomu osobye situacii mozhno ispol'zovat' dlya celej, nikak ne svyazannyh s obrabotkoj oshibok ($$9.5). Vse-taki osnovnym naznacheniem mehanizma osobyh situacij i temoj etoj glavy budet obrabotka oshibok i sozdanie ustojchivyh k oshibkam programm. 9.2 Razlichenie osobyh situacij Estestvenno, v programme vozmozhny neskol'ko razlichnyh dinamicheskih oshibok. |ti oshibki mozhno sopostavit' s osobymi situaciyami, imeyushchimi razlichnye imena. Tak, v klasse Vector obychno prihoditsya vyyavlyat' i soobshchat' ob oshibkah dvuh vidov: oshibki diapazona i oshibki, vyzvannye nepodhodyashchim dlya konstruktora parametrom: class Vector { int* p; int sz; public: enum { max = 32000 }; class Range { }; // osobaya situaciya indeksa class Size { }; // osobaya situaciya "nevernyj razmer" Vector(int sz); int& operator[](int i); // ... }; Kak bylo skazano, operaciya indeksacii zapuskaet osobuyu situaciyu Range, esli ej zadan vyhodyashchij iz diapazona znachenij indeks. Konstruktor zapuskaet osobuyu situaciyu Size, esli emu zadan nedopustimyj razmer vektora: Vector::Vector(int sz) { if (sz<0 || max<sz) throw Size(); // ... } Pol'zovatel' klassa Vector mozhet razlichit' eti dve osobye situacii, esli v proveryaemom bloke (t.e. v bloke operatora try) ukazhet obrabotchiki dlya obeih situacij: void f() { try { use_vectors(); } catch (Vector::Range) { // ... } catch (Vector::Size) { // ... } } V zavisimosti ot osoboj situacii budet vypolnyat'sya sootvetstvuyushchij obrabotchik. Esli upravlenie dojdet do konca operatorov obrabotchika, sleduyushchim budet vypolnyat'sya operator, kotoryj idet posle spiska obrabotchikov: void f() { try { use_vectors(); } catch (Vector::Range) { // ispravit' indeks i // poprobovat' opyat': f(); } catch (Vector::Size) { cerr << "Oshibka v konstruktore Vector::Size"; exit(99); } // syuda my popadem, esli voobshche ne bylo osobyh situacij // ili posle obrabotki osoboj situacii Range } Spisok obrabotchikov napominaet pereklyuchatel', no zdes' v tele obrabotchika operatory break ne nuzhny. Sintaksis spiska obrabotchikov otlichen ot sintaksisa variantov case pereklyuchatelya chastichno po etoj prichine, chastichno potomu, chtoby pokazat', chto kazhdyj obrabotchik opredelyaet svoyu oblast' vidimosti (sm. $$9.8). Ne obyazatel'no vse osobye situacii perehvatyvat' v odnoj funkcii: void f1() { try { f2(v); } catch (Vector::Size) { // ... } } void f2(Vector& v) { try { use_vectors(); } catch (Vector::Range) { // ... } } Zdes' f2() perehvatit osobuyu situaciyu Range, voznikayushchuyu v use_vectors(), a osobaya situaciya Size budet ostavlena dlya f1(). S tochki zreniya yazyka osobaya situaciya schitaetsya obrabotannoj srazu pri vhode v telo ee obrabotchika. Poetomu vse osobye situacii, zapuskaemye pri vypolnenii etogo obrabotchika, dolzhny obrabatyvat'sya v funkciyah, vyzvavshih tu funkciyu, kotoraya soderzhit proveryaemyj blok. Znachit v sleduyushchem primere ne vozniknet beskonechnogo cikla: try { // ... } catch (input_overflow) { // ... throw input_overflow(); } Zdes' input_overflow (perepolnenie pri vvode) - imya global'nogo klassa. Obrabotchiki osobyh situacij mogut byt' vlozhennymi: try { // ... } catch (xxii) { try { // slozhnaya reakciya } catch (xxii) { // oshibka v processe slozhnoj reakcii } } Odnako, takaya vlozhennost' redko byvaet nuzhna v obychnyh programmah, i chashche vsego ona yavlyaetsya svidetel'stvom plohogo stilya. 9.3 Imena osobyh situacij Osobaya situaciya perehvatyvaetsya blagodarya svoemu tipu. Odnako, zapuskaetsya ved' ne tip, a ob容kt. Esli nam nuzhno peredat' nekotoruyu informaciyu iz tochki zapuska v obrabotchik, to dlya etogo ee sleduet pomestit' v zapuskaemyj ob容kt. Naprimer, dopustim nuzhno znat' znachenie indeksa, vyhodyashchee za granicy diapazona: class Vector { // ... public: class Range { public: int index; Range(int i) : index(i) { } }; // ... int& operator[](int i) // ... }; int Vector::operator[](int i) { if (o<=i && i <sz) return p[i]; throw Range(i); } CHtoby issledovat' nedopustimoe znachenie indeksa, v obrabotchike nuzhno dat' imya ob容ktu, predstavlyayushchemu osobuyu situaciyu: void f(Vector& v) { // ... try { do_something(v); } catch (Vector::Range r ) { cerr << "nedopustimyj indeks" << r.index << '\n'; // ... } // ... } Konstrukciya v skobkah posle sluzhebnogo slova catch yavlyaetsya po suti opisaniem i ona analogichna opisaniyu formal'nogo parametra funkcii. V nej ukazyvaetsya kakim mozhet byt' tip parametra (t.e. osoboj situacii) i mozhet zadavat'sya imya dlya fakticheskoj, t.e. zapushchennoj, osoboj situacii. Vspomnim, chto v shablonah tipov u nas byl vybor dlya imenovaniya osobyh situacij. V kazhdom sozdannom po shablonu klasse byl svoj klass osoboj situacii: template<class T> class Allocator { // ... class Exhausted { } // ... T* get(); }; void f(Allocator<int>& ai, Allocator<double>& ad) { try { // ... } catch (Allocator<int>::Exhausted) { // ... } catch (Allocator<double>::Exhausted) { // ... } } S drugoj storony, osobaya situaciya mozhet byt' obshchej dlya vseh sozdannyh po shablonu klassov: class Allocator_Exhausted { }; template<class T> class Allocator { // ... T* get(); }; void f(Allocator<int>& ai, Allocator<double>& ad) { try { // ... } catch (Allocator_Exhausted) { // ... } } Kakoj sposob zadaniya osoboj situacii predpochtitel'nej, skazat' trudno. Vybor zavisit ot naznacheniya rassmatrivaemogo shablona. 9.3.1 Gruppirovanie osobyh situacij Osobye situacii estestvennym obrazom razbivayutsya na semejstva. Dejstvitel'no, logichno predstavlyat' semejstvo Matherr, v kotoroe vhodyat Overflow (perepolnenie), Underflow (poterya znachimosti) i nekotorye drugie osobye situacii. Semejstvo Matherr obrazuyut osobye situacii, kotorye mogut zapuskat' matematicheskie funkcii standartnoj biblioteki. Odin iz sposobov zadaniya takogo semejstva svoditsya k opredeleniyu Matherr kak tipa, vozmozhnye znacheniya kotorogo vklyuchayut Overflow i vse ostal'nye: enum { Overflow, Underflow, Zerodivide, /* ... */ }; try { // ... } catch (Matherr m) { switch (m) { case Overflow: // ... case Underflow: // ... // ... } // ... } Drugoj sposob predpolagaet ispol'zovanie nasledovaniya i virtual'nyh funkcij, chtoby ne vvodit' pereklyuchatelya po znacheniyu polya tipa. Nasledovanie pomogaet opisat' semejstva osobyh situacij: class Matherr { }; class Overflow: public Matherr { }; class Underflow: public Matherr { }; class Zerodivide: public Matherr { }; // ... CHasto byvaet tak, chto nuzhno obrabotat' osobuyu situaciyu Matherr ne zavisimo ot togo, kakaya imenno situaciya iz etogo semejstva proizoshla. Nasledovanie pozvolyaet sdelat' eto prosto: try { // ... } catch (Overflow) { // obrabotka Overflow ili lyuboj proizvodnoj situacii } catch (Matherr) { // obrabotka lyuboj otlichnoj ot Overflow situacii } V etom primere Overflow razbiraetsya otdel'no, a vse drugie osobye situacii iz Matherr razbirayutsya kak odin obshchij sluchaj. Konechno, funkciya, soderzhashchaya catch (Matherr), ne budet znat' kakuyu imenno osobuyu situaciyu ona perehvatyvaet. No kakoj by ona ni byla, pri vhode v obrabotchik peredavaemaya ee kopiya budet Matherr. Obychno eto kak raz to, chto nuzhno. Esli eto ne tak, osobuyu situaciyu mozhno perehvatit' po ssylke (sm. $$9.3.2). Ierarhicheskoe uporyadochivanie osobyh situacij mozhet igrat' vazhnuyu rol' dlya sozdaniya yasnoj struktury programmy. Dejstvitel'no, pust' takoe uporyadochivanie otsutstvuet, i nuzhno obrabotat' vse osobye situacii standartnoj biblioteki matematicheskih funkcij. Dlya etogo pridetsya do beskonechnosti perechislyat' vse vozmozhnye osobye situacii: try { // ... } catch (Overflow) { /* ... */ } catch (Underflow) { /* ... */ } catch (Zerodivide) { /* ... */ } // ... |to ne tol'ko utomitel'no, no i opasno, poskol'ku mozhno zabyt' kakuyu-nibud' osobuyu situaciyu. Krome togo, neobhodimost' perechislit' v proveryaemom bloke vse osobye situacii prakticheski garantiruet, chto, kogda semejstvo osobyh situacij biblioteki rasshiritsya, v programme pol'zovatelya vozniknet oshibka. |to znachit, chto pri vvedenii novoj osoboj situacii v biblioteki matematicheskih funkcij pridetsya peretranslirovat' vse chasti programmy, kotorye soderzhat obrabotchiki vseh osobyh situacij iz Matherr. V obshchem sluchae takaya peretranslyaciya nepriemlema. CHasto dazhe net vozmozhnosti najti vse trebuyushchie peretranslyacii chasti programmy. Esli takaya vozmozhnost' est', nel'zya trebovat', chtoby vsegda byl dostupen ishodnoj tekst lyuboj chasti bol'shoj programmy, ili chtoby u nas byli prava izmenyat' lyubuyu chast' bol'shoj programmy, ishodnyj tekst kotoroj my imeem. Na samom dele, pol'zovatel' ne dolzhen dumat' o vnutrennem ustrojstve bibliotek. Vse eti problemy peretranslyacii i soprovozhdeniya mogut privesti k tomu, chto posle sozdaniya pervoj versii biblioteki budet nel'zya vvodit' v nej novye osobye situacii. No takoe reshenie ne podhodit prakticheski dlya vseh bibliotek. Vse eti dovody govoryat za to, chto osobye situacii nuzhno opredelyat' kak ierarhiyu klassov (sm. takzhe $$9.6.1). |to, v svoyu ochered', oznachaet, chto osobye situacii mogut byt' chlenami neskol'kih grupp: class network_file_err // oshibki fajlovoj sistemy v seti : public network_err, // oshibki seti public file_system_err { // oshibki fajlovoj sistemy // ... }; Osobuyu situaciyu network_file_err mozhno perehvatit' v funkciyah, obrabatyvayushchih osobye situacii seti: void f() { try { // kakie-to operatory } catch (network_err) { // ... } } Ee takzhe mozhno perehvatit' v funkciyah, obrabatyvayushchih osobye situacii fajlovoj sistemy: void g() { try { // kakie-to drugie operatory } catch (file_system_err) { // ... } } |to vazhnyj moment, poskol'ku takoj sistemnyj servis kak rabota v seti dolzhen byt' prozrachen, a eto oznachaet, chto sozdatel' funkcii g() mozhet dazhe i ne znat', chto eta funkciya budet vypolnyat'sya v setevom rezhime. Otmetim, chto v nastoyashchee vremya net standartnogo mnozhestva osobyh situacij dlya standartnoj matematicheskoj biblioteki i biblioteki vvoda-vyvoda. Zadacha komitetov ANSI i ISO po standartizacii S++ reshit' nuzhno li takoe mnozhestvo i kakie v nem sleduet ispol'zovat' imena i klassy. Poskol'ku mozhno srazu perehvatit' vse osobye situacii (sm. $$9.3.2), net nastoyatel'noj neobhodimosti sozdavat' dlya etoj celi obshchij, bazovyj dlya vseh osobyh situacij, klass. Odnako, esli vse osobye situacii yavlyayutsya proizvodnymi ot pustogo klassa Exception (osobaya situaciya), to v interfejsah ih ispol'zovanie stanovitsya bolee regulyarnym (sm. $$9.6). Esli vy ispol'zuete obshchij bazovyj klass Exception, ubedites', chto v nem nichego net krome virtual'nogo destruktora. V protivnom sluchae takoj klass mozhet vstupit' v protivorechie s predpolagaemym standartom. 9.3.2 Proizvodnye osobye situacii Esli dlya obrabotki osobyh situacij my ispol'zuem ierarhiyu klassov, to, estestvenno, kazhdyj obrabotchik dolzhen razbirat'sya tol'ko s chast'yu informacii, peredavaemoj pri osobyh situaciyah. Mozhno skazat', chto, kak pravilo, osobaya situaciya perehvatyvaetsya obrabotchikom ee bazovogo klassa, a ne obrabotchikom klassa, sootvetstvuyushchego imenno etoj osoboj situacii. Imenovanie i perehvat obrabotchikom osoboj situacii semanticheski ekvivalentno imenovaniyu i polucheniyu parametra v funkcii. Proshche govorya, formal'nyj parametr inicializiruetsya znacheniem fakticheskogo parametra. |to oznachaet, chto zapushchennaya osobaya situaciya "nizvoditsya" do osoboj situacii, ozhidaemoj obrabotchikom. Naprimer: class Matherr { // ... virtual void debug_print(); }; class Int_overflow : public Matherr { public: char* op; int opr1, opr2;; int_overflow(const char* p, int a, int b) { cerr << op << '(' << opr1 << ',' << opr2 << ')'; } }; void f() { try { g(); } catch (Matherr m) { // ... } } Pri vhode v obrabotchik Matherr osobaya situaciya m yavlyaetsya ob容ktom Matherr, dazhe esli pri obrashchenii k g() byla zapushchena Int_overflow. |to oznachaet, chto dopolnitel'naya informaciya, peredavaemaya v Int_overflow, nedostupna. Kak obychno, chtoby imet' dostup k dopolnitel'noj informacii mozhno ispol'zovat' ukazateli ili ssylki. Poetomu mozhno bylo napisat' tak: int add(int x, int y) // slozhit' x i y s kontrolem { if (x > 0 && y > 0 && x > MAXINT - y || x < 0 && y < 0 && x < MININT + y) throw Int_overflow("+", x, y); // Syuda my popadaem, libo kogda proverka // na perepolnenie dala otricatel'nyj rezul'tat, // libo kogda x i y imeyut raznye znaki return x + y; } void f() { try { add(1,2); add(MAXINT,-2); add(MAXINT,2); // a dal'she - perepolnenie } catch (Matherr& m) { // ... m.debug_print(); } } Zdes' poslednee obrashchenie k add privedet k zapusku osoboj situacii, kotoryj, v svoyu ochered', privedet k vyzovu Int_overflow::debug_print(). Esli by osobaya situaciya peredavalas' po znacheniyu, a ne po ssylke, to byla by vyzvana funkciya Matherr::debug_print(). Neredko byvaet tak, chto perehvativ osobuyu situaciyu, obrabotchik reshaet, chto s etoj oshibkoj on nichego ne smozhet podelat'. V takom sluchae samoe estestvennoe zapustit' osobuyu situaciyu snova v nadezhde, chto s nej sumeet razobrat'sya drugoj obrabotchik: void h() { try { // kakie-to operatory } catch (Matherr) { if (can_handle_it) { // esli obrabotka vozmozhna, // sdelat' ee } else { throw; // povtornyj zapusk perehvachennoj // osoboj situacii } } } Povtornyj zapusk zapisyvaetsya kak operator throw bez parametrov. Pri etom snova zapuskaetsya ishodnaya osobaya situaciya, kotoraya byla perehvachena, a ne ta ee chast', na kotoruyu rasschitan obrabotchik Matherr. Inymi slovami, esli byla zapushchena Int_overflow, vyzyvayushchaya h() funkciya mogla by perehvatit' ee kak Int_overflow, nesmotrya na to, chto ona byla perehvachena v h() kak Matherr i zapushchena snova: void k() { try { h(); // ... } catch (Int_overflow) { // ... } } Polezen vyrozhdennyj sluchaj perezapuska. Kak i dlya funkcij, ellipsis ... dlya obrabotchika oznachaet "lyuboj parametr", poetomu operator catch (...) oznachaet perehvat lyuboj osoboj situacii: void m() { try { // kakie-to operatory } catch (...) { // privesti vse v poryadok throw; } } |tot primer nado ponimat' tak: esli pri vypolnenii osnovnoj chasti m() voznikaet osobaya situaciya, vypolnyaetsya obrabotchik, kotorye vypolnyaet obshchie dejstviya po ustraneniyu posledstvij osoboj situacii, posle etih dejstvij osobaya situaciya, vyzvavshaya ih, zapuskaetsya povtorno. Poskol'ku obrabotchik mozhet perehvatit' proizvodnye osobye situacii neskol'kih tipov, poryadok, v kotorom idut obrabotchiki v proveryaemom bloke, sushchestvenen. Obrabotchiki pytayutsya perehvatit' osobuyu situaciyu v poryadke ih opisaniya. Privedem primer: try { // ... } catch (ibuf) { // obrabotka perepolneniya bufera vvoda } catch (io) { // obrabotka lyuboj oshibki vvoda-vyvoda } catch (stdlib) { // obrabotka lyuboj osoboj situacii v biblioteke } catch (...) { // obrabotka vseh ostal'nyh osobyh situacij } Tip osoboj situacii v obrabotchike sootvetstvuet tipu zapushchennoj osoboj situacii v sleduyushchih sluchayah: esli eti tipy sovpadayut, ili vtoroj tip yavlyaetsya tipom dostupnogo bazovogo klassa zapushchennoj situacii, ili on yavlyaetsya ukazatelem na takoj klass, a tip ozhidaemoj situacii tozhe ukazatel' ($$R.4.6). Poskol'ku translyatoru izvestna ierarhiya klassov, on sposoben obnaruzhit' takie nelepye oshibki, kogda obrabotchik catch (...) ukazan ne poslednim, ili kogda obrabotchik situacii bazovogo klassa predshestvuet obrabotchiku proizvodnoj ot etogo klassa situacii ($$R15.4). V oboih sluchaya, posleduyushchij obrabotchik (ili obrabotchiki) ne mogut byt' zapushcheny, poskol'ku oni "maskiruyutsya" pervym obrabotchikom. 9.4 Zaprosy resursov Esli v nekotoroj funkcii potrebuyutsya opredelennye resursy, naprimer, nuzhno otkryt' fajl, otvesti blok pamyati v oblasti svobodnoj pamyati, ustanovit' monopol'nye prava dostupa i t.d., dlya dal'nejshej raboty sistemy obychno byvaet krajne vazhno, chtoby resursy byli osvobozhdeny nadlezhashchim obrazom. Obychno takoj "nadlezhashchij sposob" realizuet funkciya, v kotoroj proishodit zapros resursov i osvobozhdenie ih pered vyhodom. Naprimer: void use_file(const char* fn) { FILE* f = fopen(fn,"w"); // rabotaem s f fclose(f); } Vse eto vyglyadit vpolne normal'no do teh por, poka vy ne pojmete, chto pri lyuboj oshibke, proisshedshej posle vyzova fopen() i do vyzova fclose(), vozniknet osobaya situaciya, v rezul'tate kotoroj my vyjdem iz use_file(), ne obrashchayas' k fclose().X X Stoit skazat', chto ta zhe problema voznikaet i v yazykah, ne podderzhivayushchih osobye situacii. Tak, obrashchenie k funkcii longjump() iz standartnoj biblioteki S mozhet imet' takie zhe nepriyatnye posledstviya. Esli vy sozdaete ustojchivuyu k oshibkam sistemam, etu problemu pridetsya reshat'. Mozhno dat' primitivnoe reshenie: void use_file(const char* fn) { FILE* f = fopen(fn,"w");