lee vazhna i chashche dostizhima dlya realizacij, chem dlya interfejsov. Otmetim, chto klass opredelyaet tri interfejsa: class X { private: // dostupno tol'ko dlya chlenov i druzej protected: // dostupno tol'ko dlya chlenov i druzej, a takzhe // dlya chlenov i druzej proizvodnyh klassov public: // obshchedostupno }; CHleny dolzhny obrazovyvat' samyj ogranichennyj iz vozmozhnyh interfejsov. Inymi slovami, chlen dolzhen byt' opisan kak private, esli net prichin dlya bolee shirokogo dostupa k nemu; esli zhe takovye est', to chlen dolzhen byt' opisan kak protected, esli net dopolnitel'nyh prichin zadat' ego kak public. V bol'shinstve sluchaev ploho zadavat' vse dannye, predstavlyaemye chlenami, kak public. Funkcii i klassy, obrazuyushchie obshchij interfejs, dolzhny byt' sproektirovany takim obrazom, chtoby predstavlenie klassa sovpadalo s ego rol'yu v proekte kak sredstva predstavleniya ponyatij. Napomnim, chto druz'ya yavlyayutsya chast'yu obshchego interfejsa. Otmetim, chto abstraktnye klassy mozhno ispol'zovat' dlya predstavleniya ponyatiya upryatyvaniya bolee vysokogo urovnya ($$1.4.6, $$6.3, $$13.3). 12.5 Svod pravil V etoj glave my kosnulis' mnogih tem, no, kak pravilo, izbegali davat' nastoyatel'nye i konkretnye rekomendacii po rassmatrivaemym voprosam. |to otvechaet moemu ubezhdeniyu, chto net "edinstvenno vernogo resheniya". Principy i priemy sleduet primenyat' sposobom, naibolee podhodyashchim dlya konkretnoj zadachi. Zdes' trebuyutsya vkus, opyt i razum. Tem ne menee, mozhno predlozhit' svod pravil, kotorye razrabotchik mozhet ispol'zovat' v kachestve orientirov, poka ne priobretet dostatochno opyta, chtoby vyrabotat' luchshie. |tot svod pravil privoditsya nizhe. On mozhet sluzhit' otpravnoj tochkoj v processe vyrabotki osnovnyh napravlenij proekta konkretnoj zadachi, ili zhe on mozhet ispol'zovat'sya organizaciej v kachestve proverochnogo spiska. Podcherknu eshche raz, chto eti pravila ne yavlyayutsya universal'nymi i ne mogut zamenit' soboj razmyshleniya. - Nacelivajte pol'zovatelya na primenenie abstrakcii dannyh i ob容ktno-orientirovannogo programmirovaniya. - Postepenno perehodite na novye metody, ne speshite. - Ispol'zujte vozmozhnosti S++ i metody ob枸ktno-orientirovannogo programmirovaniya tol'ko po mere nadobnosti. _ Dobejtes' sootvetstviya stilya proekta i programmy. - Koncentrirujte vnimanie na proektirovanii komponenta. _ Ispol'zujte klassy dlya predstavleniya ponyatij. - Ispol'zujte obshchee nasledovanie dlya predstavleniya otnoshenij "est'". - Ispol'zujte prinadlezhnost' dlya predstavleniya otnoshenij "imeet". - Ubedites', chto otnosheniya ispol'zovaniya ponyatny, ne obrazuyut ciklov, i chto chislo ih minimal'no. - Aktivno ishchite obshchnost' sredi ponyatij oblasti prilozheniya i realizacii, i voznikayushchie v rezul'tate bolee obshchie ponyatiya predstavlyajte kak bazovye klassy. - Opredelyajte interfejs tak, chtoby otkryvat' minimal'noe kolichestvo trebuemoj informacii: - Ispol'zujte, vsyudu gde eto mozhno, chastnye dannye i funkcii-chleny. - Ispol'zujte opisaniya public ili protected, chtoby otlichit' zaprosy razrabotchika proizvodnyh klassov ot zaprosov obychnyh pol'zovatelej. - Svedite k minimumu zavisimosti odnogo interfejsa ot drugih. - Podderzhivajte stroguyu tipizaciyu interfejsov. - Zadavajte interfejsy v terminah tipov iz oblasti prilozheniya. Dopolnitel'nye pravila mozhno najti $$11.5.  * PROEKTIROVANIE BIBLIOTEK Proekt biblioteki - eto proekt yazyka, (fol'klor firmy Bell Laboratories) ... i naoborot. - A. Kenig |ta glava soderzhit opisanie razlichnyh priemov, okazavshihsya poleznymi pri sozdanii bibliotek dlya yazyka S++. V chastnosti, v nej rassmatrivayutsya konkretnye tipy, abstraktnye tipy, uzlovye klassy, upravlyayushchie klassy i interfejsnye klassy. Pomimo etogo obsuzhdayutsya ponyatiya obshirnogo interfejsa i struktury oblasti prilozheniya, ispol'zovanie dinamicheskoj informacii o tipah i metody upravleniya pamyat'yu. Vnimanie akcentiruetsya na tom, kakimi svojstvami dolzhny obladat' bibliotechnye klassy, a ne na specifike yazykovyh sredstv, kotorye ispol'zuyutsya dlya realizacii takih klassov, i ne na opredelennyh poleznyh funkciyah, kotorye dolzhna predostavlyat' biblioteka. 13.1 Vvedenie Razrabotka biblioteki obshchego naznacheniya - eto gorazdo bolee trudnaya zadacha, chem sozdanie obychnoj programmy. Programma - eto reshenie konkretnoj zadachi dlya konkretnoj oblasti prilozheniya, togda kak biblioteka dolzhna predostavlyat' vozmozhnost' reshenie dlya mnozhestva zadach, svyazannyh s mnogimi oblastyami prilozheniya. V obychnoj programme pozvolitel'ny sil'nye dopushcheniya ob ee okruzhenii, togda kak horoshuyu biblioteku mozhno uspeshno ispol'zovat' v raznoobraznyh okruzheniyah, sozdavaemyh mnozhestvom razlichnyh programm. CHem bolee obshchej i poleznoj okazhetsya biblioteka, tem v bol'shem chisle okruzhenij ona budet proveryat'sya, i tem zhestche budut trebovaniya k ee korrektnosti, gibkosti, effektivnosti, rasshiryaemosti, perenosimosti, neprotivorechivosti, prostote, polnote, legkosti ispol'zovaniya i t.d. Vse zhe biblioteka ne mozhet dat' vam vse, poetomu nuzhen opredelennyj kompromiss. Biblioteku mozhno rassmatrivat' kak special'nyj, interesnyj variant togo, chto v predydushchej glave my nazyvali komponentom. Kazhdyj sovet po proektirovaniyu i soprovozhdeniyu komponentov stanovitsya predel'no vazhnym dlya bibliotek, i, naoborot, mnogie metody postroeniya bibliotek nahodyat primenenie pri proektirovanii razlichnyh komponentov. Bylo by slishkom samonadeyanno ukazyvat' kak sleduet konstruirovat' biblioteki. V proshlom okazalis' uspeshnymi neskol'ko razlichnyh metodov, a sam predmet ostaetsya polem aktivnyh diskussij i eksperimentov. Zdes' tol'ko obsuzhdayutsya nekotorye vazhnye aspekty etoj zadachi i predlagayutsya nekotorye priemy, okazavshiesya poleznymi pri sozdanii bibliotek. Ne sleduet zabyvat', chto biblioteki prednaznacheny dlya sovershenno raznyh oblastej programmirovaniya, poetomu ne prihoditsya rasschityvat', chto kakoj-to odin metod okazhetsya naibolee priemlemym dlya vseh bibliotek. Dejstvitel'no, net nikakih prichin polagat', chto metody, okazavshiesya poleznymi pri realizacii sredstv parallel'nogo programmirovaniya dlya yadra mnogoprocessornoj operacionnoj sistemy, okazhutsya naibolee priemlemymi pri sozdanii biblioteki, prednaznachennoj dlya resheniya nauchnyh zadach, ili biblioteki, predstavlyayushchej graficheskij interfejs. Ponyatie klassa S++ mozhet ispol'zovat'sya samymi raznymi sposobami, poetomu raznoobrazie stilej programmirovaniya mozhet privesti k besporyadku. Horoshaya biblioteka dlya svedeniya takogo besporyadka k minimumu obespechivaet soglasovannyj stil' programmirovaniya, ili, po krajnej mere, neskol'ko takih stilej. |tot podhod delaet biblioteku bolee "predskazuemoj", a znachit pozvolyaet legche i bystree izuchit' ee i pravil'no ispol'zovat'. Dalee opisyvayutsya pyat' "arhitipichnyh" klassov, i obsuzhdayutsya prisushchie im sil'nye i slabye storony: konkretnye tipy ($$13.2), abstraktnye tipy ($$13.3), uzlovye klassy ($$13.4), interfejsnye klassy ($$13.8), upravlyayushchie klassy ($$13.9). Vse eti vidy klassov otnosyatsya k oblasti ponyatij, a ne yavlyayutsya konstrukciyami yazyka. Kazhdoe ponyatie voploshchaetsya s pomoshch'yu osnovnoj konstrukcii - klassa. V ideale nado imet' minimal'nyj nabor prostyh i ortogonal'nyh vidov klassov, ishodya iz kotorogo mozhno postroit' lyuboj poleznyj i razumno-opredelennyj klass. Ideal nami ne dostignut i, vozmozhno, nedostizhim voobshche. Vazhno ponyat', chto lyuboj iz perechislennyh vidov klassov igraet svoyu rol' pri proektirovanii biblioteki i, esli rasschityvat' na obshchee primenenie, nikakoj iz nih ne yavlyaetsya po svoej suti luchshe drugih. V etoj glave vvoditsya ponyatie obshirnogo interfejsa ($$13.6), chtoby vydelit' nekotoryj obshchij sluchaj vseh etih vidov klassov. S pomoshch'yu nego opredelyaetsya ponyatie karkasa oblasti prilozheniya ($$13.7). Zdes' rassmatrivayutsya prezhde vsego klassy, otnosyashchiesya strogo k odnomu iz perechislennyh vidov, hotya, konechno, ispol'zuyutsya klassy i gibridnogo vida. No ispol'zovanie klassa gibridnogo vida dolzhno byt' rezul'tatom osoznannogo resheniya, voznikshego pri ocenke plyusov i minusov razlichnyh vidov, a ne rezul'tatom pagubnogo stremleniya uklonit'sya ot vybora vida klassa (slishkom chasto "otlozhim poka vybor" oznachaet prosto nezhelanie dumat'). Neiskushennym razrabotchikam biblioteki luchshe vsego derzhat'sya podal'she ot klassov gibridnogo vida. Im mozhno posovetovat' sledovat' stilyu programmirovaniya toj iz sushchestvuyushchih bibliotek, kotoraya obladaet vozmozhnostyami, neobhodimymi dlya proektiruemoj biblioteki. Otvazhit'sya na sozdanie biblioteki obshchego naznacheniya mozhet tol'ko iskushennyj programmist, i kazhdyj sozdatel' biblioteki vposledstvii budet "osuzhden" na dolgie gody ispol'zovaniya, dokumentirovaniya i soprovozhdeniya svoego sobstvennogo sozdaniya. V yazyke S++ ispol'zuyutsya staticheskie tipy. Odnako, inogda voznikaet neobhodimost' v dopolnenie k vozmozhnostyam, neposredstvenno predostavlyaemym virtual'nymi funkciyami, poluchat' dinamicheskuyu informaciyu o tipah. Kak eto sdelat', opisano v $$13.5. Nakonec, pered vsyakoj netrivial'noj bibliotekoj vstaet zadacha upravleniya pamyat'yu. Priemy ee resheniya rassmatrivayutsya v $$13.10. Estestvenno, v etoj glave nevozmozhno rassmotret' vse metody, okazavshiesya poleznymi pri sozdanii biblioteki. Poetomu mozhno otoslat' k drugim mestam knigi, gde rassmotreny sleduyushchie voprosy: rabota s oshibkami i ustojchivost' k oshibkam ($$9.8), ispol'zovanie funkcional'nyh ob容ktov i obratnyh vyzovov ($$10.4.2 i $$9.4.3) , ispol'zovanie shablonov tipa dlya postroeniya klassov ($$8.4). Mnogie temy etoj glavy svyazany s klassami, yavlyayushchimisya kontejnerami, (naprimer, massivy i spiski). Konechno, takie kontejnernye klassy yavlyayutsya shablonami tipa (kak bylo skazano v $$1.i 4.3 $$8). No zdes' dlya uproshcheniya izlozheniya v primerah ispol'zuyutsya klassy, soderzhashchie ukazateli na ob容kty tipa klass. CHtoby poluchit' nastoyashchuyu programmu, nado ispol'zovat' shablony tipa, kak pokazano v glave 8. 13.2 Konkretnye tipy Takie klassy kak vector ($$1.4), Slist ($$8.3), date ($$5.2.2) i complex ($$7.3) yavlyayutsya konkretnymi v tom smysle, chto kazhdyj iz nih predstavlyaet dovol'no prostoe ponyatie i obladaet neobhodimym naborom operacij. Imeetsya vzaimnoodnoznachnoe sootvetstvie mezhdu interfejsom klassa i ego realizaciej. Ni odin iz nih (iznachal'no) ne prednaznachalsya v kachestve bazovogo dlya polucheniya proizvodnyh klassov. Obychno v ierarhii klassov konkretnye tipy stoyat osobnyakom. Kazhdyj konkretnyj tip mozhno ponyat' izolirovanno, vne svyazi s drugimi klassami. Esli realizaciya konkretnogo tipa udachna, to rabotayushchie s nim programmy sravnimy po razmeru i skorosti so sdelannymi vruchnuyu programmami, v kotoryh ispol'zuetsya nekotoraya special'naya versiya obshchego ponyatiya. Dalee, esli proizoshlo znachitel'noe izmenenie realizacii, obychno modificiruetsya i interfejs, chtoby otrazit' eti izmeneniya. Interfejs, po svoej suti, obyazan pokazat' kakie izmeneniya okazalis' sushchestvennymi v dannom kontekste. Interfejs bolee vysokogo urovnya ostavlyaet bol'she svobody dlya izmeneniya realizacii, no mozhet uhudshit' harakteristiki programmy. Bolee togo, horoshaya realizaciya zavisit tol'ko ot minimal'nogo chisla dejstvitel'no sushchestvennyh klassov. Lyuboj iz etih klassov mozhno ispol'zovat' bez nakladnyh rashodov, voznikayushchih na etape translyacii ili vypolneniya, i vyzvannyh prisposobleniem k drugim, "shodnym" klassam programmy. Podvodya itog, mozhno ukazat' takie usloviya, kotorym dolzhen udovletvoryat' konkretnyj tip: [1] polnost'yu otrazhat' dannoe ponyatie i metod ego realizacii; [2] s pomoshch'yu podstanovok i operacij, polnost'yu ispol'zuyushchih poleznye svojstva ponyatiya i ego realizacii, obespechivat' effektivnost' po skorosti i pamyati, sravnimuyu s "ruchnymi programmami"; [3] imet' minimal'nuyu zavisimost' ot drugih klassov; [4] byt' ponyatnym i poleznym dazhe izolirovanno. Vse eto dolzhno privesti k tesnoj svyazi mezhdu pol'zovatelem i programmoj, realizuyushchej konkretnyj tip. Esli v realizacii proizoshli izmeneniya, programmu pol'zovatelya pridetsya peretranslirovat', poskol'ku v nej navernyaka soderzhatsya vyzovy funkcij, realizuemye podstanovkoj, a takzhe lokal'nye peremennye konkretnogo tipa. Dlya nekotoryh oblastej prilozheniya konkretnye tipy obespechivayut osnovnye tipy, pryamo ne predstavlennye v S++, naprimer: kompleksnye chisla, vektora, spiski, matricy, daty, associativnye massivy, stroki simvolov i simvoly, iz drugogo (ne anglijskogo) alfavita. V mire, sostoyashchem iz konkretnyh ponyatij, na samom dele net takoj veshchi kak spisok. Vmesto etogo est' mnozhestvo spisochnyh klassov, kazhdyj iz kotoryh specializiruetsya na predstavlenii kakoj-to versii ponyatiya spisok. Sushchestvuet dyuzhina spisochnyh klassov, v tom chisle: spisok s odnostoronnej svyaz'yu; spisok s dvustoronnej svyaz'yu; spisok s odnostoronnej svyaz'yu, v kotorom pole svyazi ne prinadlezhit ob容ktu; spisok s dvustoronnej svyaz'yu, v kotorom polya svyazi ne prinadlezhat ob容ktu; spisok s odnostoronnej svyaz'yu, dlya kotorogo mozhno prosto i effektivno opredelit' vhodit li v nego dannyj ob容kt; spisok s dvustoronnej svyaz'yu, dlya kotorogo mozhno prosto i effektivno opredelit' vhodit li v nego dannyj ob容kt i t.d. Nazvanie "konkretnyj tip" (CDT - concrete data type, t.e. konkretnyj tip dannyh) , bylo vybrano po kontrastu s terminom "abstraktnyj tip" (ADT - abstract data type, t.e. abstraktnyj tip dannyh). Otnosheniya mezhdu CDT i ADT obsuzhdayutsya v $$13.3. Sushchestvenno, chto konkretnye tipy ne prednaznacheny dlya yavnogo vyrazheniya nekotoroj obshchnosti. Tak, tipy slist i vector mozhno ispol'zovat' v kachestve al'ternativnoj realizacii ponyatiya mnozhestva, no v yazyke eto yavno ne otrazhaetsya. Poetomu, esli programmist hochet rabotat' s mnozhestvom, ispol'zuet konkretnye tipy i ne imeet opredeleniya klassa mnozhestvo, to on dolzhen vybirat' mezhdu tipami slist i vector. Togda programma zapisyvaetsya v terminah vybrannogo klassa, skazhem, slist, i esli potom predpochtut ispol'zovat' drugoj klass, programmu pridetsya perepisyvat'. |to potencial'noe neudobstvo kompensiruetsya nalichiem vseh "estestvennyh" dlya dannogo klassa operacij, naprimer takih, kak indeksaciya dlya massiva i udalenie elementa dlya spiska. |ti operacii predstavleny v optimal'nom variante, bez "neestestvennyh" operacij tipa indeksacii spiska ili udaleniya massiva, chto moglo by vyzvat' putanicu. Privedem primer: void my(slist& sl) { for (T* p = sl.first(); p; p = sl.next()) { // moj kod } // ... } void your(vector& v) { for (int i = 0; i<v.size(); i++) { // vash kod } // ... } Sushchestvovanie takih "estestvennyh" dlya vybrannogo metoda realizacii operacij obespechivaet effektivnost' programmy i znachitel'no oblegchaet ee napisanie. K tomu zhe, hotya realizaciya vyzova podstanovkoj obychno vozmozhna tol'ko dlya prostyh operacij tipa indeksacii massiva ili polucheniya sleduyushchego elementa spiska, ona okazyvaet znachitel'nyj effekt na skorost' vypolneniya programmy. Zagvozdka zdes' sostoit v tom, chto fragmenty programmy, ispol'zuyushchie po svoej suti ekvivalentnye operacii, kak, naprimer, dva privedennyh vyshe cikla, mogut vyglyadet' nepohozhimi drug na druga, a fragmenty programmy, v kotoryh dlya ekvivalentnyh operacij ispol'zuyutsya raznye konkretnye tipy, ne mogu zamenyat' drug druga. Obychno, voobshche, nevozmozhno svesti shodnye fragmenty programmy v odin. Pol'zovatel', obrashchayushchijsya k nekotoroj funkcii, dolzhen tochno ukazat' tip ob容kta, s kotorym rabotaet funkciya, naprimer: void user() { slist sl; vector v(100); my(sl); your(v); my(v); // oshibka: nesootvetstvie tipa your(sl); // oshibka: nesootvetstvie tipa } CHtoby kompensirovat' zhestkost' etogo trebovaniya, razrabotchik nekotoroj poleznoj funkcii dolzhen predostavit' neskol'ko ee versij, chtoby u pol'zovatelya byl vybor: void my(slist&); void my(vector&); void your(slist&); void your(vector&); void user() { slist sl; vector v(100); my(sl); your(v); my(v); // teper' normal'no: vyzov my(vector&) your(sl); // teper' normal'no: vyzov your(slist&) } Poskol'ku telo funkcii sushchestvenno zavisit ot tipa ee parametra, nado napisat' kazhduyu versiyu funkcij my() i your() nezavisimo drug ot druga, chto mozhet byt' hlopotno. S uchetom vsego izlozhennogo konkretnyj tip, mozhno skazat', pohodit na vstroennye tipy. Polozhitel'noj storonoj etogo yavlyaetsya tesnaya svyaz' mezhdu pol'zovatelem tipa i ego sozdatelem, a takzhe mezhdu pol'zovatelyami, kotorye sozdayut ob容kty dannogo tipa, i pol'zovatelyami, kotorye pishut funkcii, rabotayushchie s etimi ob容ktami. CHtoby pravil'no ispol'zovat' konkretnyj tip, pol'zovatel' dolzhen razbirat'sya v nem detal'no. Obychno ne sushchestvuet kakih-to universal'nyh svojstv, kotorymi obladali by vse konkretnye tipy biblioteki, i chto pozvolilo by pol'zovatelyu, rasschityvaya na eti svojstva, ne tratit' sily na izuchenie otdel'nyh klassov. Takova plata za kompaktnost' programmy i effektivnost' ee vypolneniya. Inogda eto vpolne razumnaya plata, inogda net. Krome togo, vozmozhen takoj sluchaj, kogda otdel'nyj konkretnyj klass proshche ponyat' i ispol'zovat', chem bolee obshchij (abstraktnyj) klass. Imenno tak byvaet s klassami, predstavlyayushchimi horosho izvestnye tipy dannyh, takie kak massivy ili spiski. Tem ne menee, ukazhem, chto v ideale nado skryvat', naskol'ko vozmozhno, detali realizacii, poka eto ne uhudshaet harakteristiki programmy. Bol'shuyu pomoshch' zdes' okazyvayut funkcii-podstanovki. Esli sdelat' otkrytymi peremennye, yavlyayushchiesya chlenami, s pomoshch'yu opisaniya public, ili neposredstvenno rabotat' s nimi s pomoshch'yu funkcij, kotorye ustanavlivayut i poluchayut znacheniya etih peremennyh, to pochti vsegda eto privodit k plohomu rezul'tatu. Konkretnye tipy dolzhny byt' vse-taki nastoyashchimi tipami, a ne prosto programmnoj kuchej s neskol'kim funkciyami, dobavlennymi radi udobstva. 13.3 Abstraktnye tipy Samyj prostoj sposob oslabit' svyaz' mezhdu pol'zovatelem klassa i ego sozdatelem, a takzhe mezhdu programmami, v kotoryh ob容kty sozdayutsya, i programmami, v kotoryh oni ispol'zuyutsya, sostoit v vvedenii ponyatiya abstraktnyh bazovyh klassov. |ti klassy predstavlyayut interfejs so mnozhestvom realizacij odnogo ponyatiya. Rassmotrim klass set, soderzhashchij mnozhestvo ob容ktov tipa T: class set { public: virtual void insert(T*) = 0; virtual void remove(T*) = 0; virtual int is_member(T*) = 0; virtual T* first() = 0; virtual T* next() = 0; virtual ~set() { } }; |tot klass opredelyaet interfejs s proizvol'nym mnozhestvom (set), opirayas' na vstroennoe ponyatie iteracii po elementam mnozhestva. Zdes' tipichno otsutstvie konstruktora i nalichie virtual'nogo destruktora, sm. takzhe $$6.7. Rassmotrim primer: class slist_set : public set, private slist { slink* current_elem; public: void insert(T*); void remove(T*); int is_member(T*); virtual T* first(); virtual T* next(); slist_set() : slist(), current_elem(0) { } }; class vector_set : public set, private vector { int current_index; public: void insert(T*); void remove(T*); int is_member(T*); T* first() { current_index = 0; return next(); } T* next(); vector_set(int initial_size) : array(initial_size), current_index(0) { } }; Realizaciya konkretnogo tipa ispol'zuetsya kak chastnyj bazovyj klass, a ne chlen klassa. |to sdelano i dlya udobstva zapisi, i potomu, chto nekotorye konkretnye tipy mogut imet' zashchishchennyj interfejs s cel'yu predostavit' bolee pryamoj dostup k svoim chlenam iz proizvodnyh klassov. Krome togo, podobnym obrazom v realizacii mogut ispol'zovat'sya nekotorye klassy, kotorye imeyut virtual'nye funkcii i ne yavlyayutsya konkretnymi tipami. Tol'ko s pomoshch'yu obrazovaniya proizvodnyh klassov mozhno v novom klasse izyashchno pereopredelit' (podavit') virtual'nuyu funkciyu klassa realizacii. Interfejs opredelyaetsya abstraktnym klassom. Teper' pol'zovatel' mozhet zapisat' svoi funkcii iz $$13.2 takim obrazom: void my(set& s) { for (T* p = s.first(); p; p = s.next()) { // moj kod } // ... } void your(set& s) { for (T* p = s.first(); p; p = s.next()) { // vash kod } // ... } Stalo ochevidnym shodstvo mezhdu dvumya funkciyami, i teper' dostatochno imet' tol'ko odnu versiyu dlya kazhdoj iz funkcij my() ili your(), poskol'ku dlya obshcheniya s slist_set i vector_set obe versii ispol'zuyut interfejs, opredelyaemyj klassom set: void user() { slist_set sl; vector_set v(100); my(sl); your(v); my(v); your(sl); } Bolee togo, sozdateli funkcij my() i your() ne obyazany znat' opisanij klassov slist_set i vector_set, i funkcii my() i your() nikoim obrazom ne zavisyat ot etih opisanij. Ih ne nado peretranslirovat' ili kak-to izmenyat', ni esli izmenilis' klassy slist_set ili vector_set ni dazhe, esli predlozhena novaya realizaciya etih klassov. Izmeneniya otrazhayutsya lish' na funkciyah, kotorye neposredstvenno ispol'zuyut eti klassy, dopustim vector_set. V chastnosti, mozhno vospol'zovat'sya tradicionnym primeneniem zagolovochnyh fajlov i vklyuchit' v programmy s funkciyami my() ili your() fajl opredelenij set.h, a ne fajly slist_set.h ili vector_set.h. V obychnoj situacii operacii abstraktnogo klassa zadayutsya kak chistye virtual'nye funkcii, i takoj klass ne imeet chlenov, predstavlyayushchih dannye (ne schitaya skrytogo ukazatelya na tablicu virtual'nyh funkcij). |to ob座asnyaetsya tem, chto dobavlenie nevirtual'noj funkcii ili chlena, predstavlyayushchego dannye, potrebuet opredelennyh dopushchenij o klasse, kotorye budut ogranichivat' vozmozhnye realizacii. Izlozhennyj zdes' podhod k abstraktnym klassam blizok po duhu tradicionnym metodam, osnovannym na strogom razdelenii interfejsa i ego realizacij. Abstraktnyj tip sluzhit v kachestve interfejsa, a konkretnye tipy predstavlyayut ego realizacii. Takoe razdelenie interfejsa i ego realizacij predpolagaet nedostupnost' operacij, yavlyayushchihsya "estestvennymi" dlya kakoj-to odnoj realizacii, no ne dostatochno obshchimi, chtoby vojti v interfejs. Naprimer, poskol'ku v proizvol'nom mnozhestve net uporyadochennosti, v interfejs set nel'zya vklyuchat' operaciyu indeksirovaniya, dazhe esli dlya realizacii konkretnogo mnozhestva ispol'zuetsya massiv. |to privodit k uhudsheniyu harakteristik programmy iz-za otsutstviya ruchnoj optimizacii. Dalee, stanovitsya kak pravilo nevozmozhnoj realizaciya funkcij podstanovkoj (esli ne schitat' kakih-to konkretnyh situacij, kogda nastoyashchij tip izvesten translyatoru), poetomu vse poleznye operacii interfejsa, zadayutsya kak vyzovy virtual'nyh funkcij. Kak i dlya konkretnyh tipov zdes' plata za abstraktnye tipy inogda priemlema, inogda slishkom vysoka. Podvodya itog, perechislim kakim celyam dolzhen sluzhit' abstraktnyj tip: [1] opredelyat' nekotoroe ponyatie takim obrazom, chto v programme mogut sosushchestvovat' dlya nego neskol'ko realizacij; [2] primenyaya virtual'nye funkcii, obespechivat' dostatochno vysokuyu stepen' kompaktnosti i effektivnosti vypolneniya programmy; [3] svodit' k minimumu zavisimost' lyuboj realizacii ot drugih klassov; [4] predstavlyat' samo po sebe osmyslennoe ponyatie. Nel'zya skazat', chto abstraktnye tipy luchshe konkretnyh tipov, eto prosto drugie tipy. Kakie iz nih predpochest' - eto, kak pravilo, trudnyj i vazhnyj vopros dlya pol'zovatelya. Sozdatel' biblioteki mozhet uklonit'sya ot otveta na nego i predostavit' varianty s obeimi tipami, tem samym vybor perekladyvaetsya na pol'zovatelya. No zdes' vazhno yasno ponimat', s klassom kakogo vida imeesh' delo. Obychno neudachej zakanchivaetsya popytka ogranichit' obshchnost' abstraktnogo tipa, chtoby skorost' programm, rabotayushchih s nim, priblizilas' k skorosti programm, rasschitannyh na konkretnyj tip. V etom sluchae nel'zya ispol'zovat' vzaimozamenyaemye realizacii bez bol'shoj peretranslyacii programmy posle vneseniya izmenenij. Stol' zhe neudachna byvaet popytka dat' "obshchnost'" v konkretnyh tipah, chtoby oni mogli po moshchnosti ponyatij priblizit'sya k abstraktnym tipam. |to snizhaet effektivnost' i primenimost' prostyh klassov. Klassy etih dvuh vidov mogut sosushchestvovat', i oni dolzhny mirno sosushchestvovat' v programme. Konkretnyj klass voploshchaet realizaciyu abstraktnogo tipa, i smeshivat' ego s abstraktnym klassom ne sleduet. Otmetim, chto ni konkretnye, ni abstraktnye tipy ne sozdayutsya iznachal'no kak bazovye klassy dlya postroeniya v dal'nejshem proizvodnyh klassov. Postroenie proizvodnyh k abstraktnym tipam klassov skoree nuzhno dlya zadaniya realizacij, chem dlya razvitiya samogo ponyatiya interfejsa. Vsyakij konkretnyj ili abstraktnyj tip prednaznachen dlya chetkogo i effektivnogo predstavleniya v programme otdel'nogo ponyatiya. Klassy, kotorym eto udaetsya, redko byvayut horoshimi kandidatami dlya sozdaniya na ih baze novyh, no svyazannyh s nimi, klassov. Dejstvitel'no, popytki postroit' proizvodnye, "bolee razvitye" klassy na baze konkretnyh ili abstraktnyh tipov, takih kak, stroki, kompleksnye chisla, spiski ili associativnye massivy privodyat obychno k gromozdkim konstrukciyam. Kak pravilo eti klassy sleduet ispol'zovat' kak chleny ili chastnye bazovye klassy, togda ih mozhno effektivno primenyat', ne vyzyvaya putanicy i protivorechij v interfejsah i realizaciyah etih i novyh klassov. Kogda sozdaetsya konkretnyj ili abstraktnyj tip, akcent sleduet sdelat' na tom, chtoby predlozhit' prostoj, realizuyushchij horosho produmannoe ponyatie, interfejs. Popytki rasshirit' oblast' prilozheniya klassa, nagruzhaya ego opisanie vsevozmozhnymi "poleznymi" svojstvami, privodyat tol'ko k besporyadku i neeffektivnosti. |tim zhe konchayutsya naprasnye usiliya garantirovat' povtornoe ispol'zovanie klassa, kogda kazhduyu funkciyu-chlen ob座avlyayut virtual'noj, ne podumav zachem i kak eti funkcii budut pereopredelyat'sya. Pochemu my ne stali opredelyat' klassy slist i vector kak pryamye proizvodnye ot klassa set, obojdyas' tem samym bez klassov slist_set i vector_set? Drugimi slovami zachem nuzhny konkretnye tipy, kogda uzhe opredeleny abstraktnye tipy? Mozhno predlozhit' tri otveta: [1] |ffektivnost': takie tipy, kak vector ili slist nado sozdavat' bez nakladnyh rashodov, vyzvannyh otdaleniem realizacij ot interfejsov (razdeleniya interfejsa i realizacii trebuet koncepciya abstraktnogo tipa). [2] Mnozhestvennyj interfejs: chasto raznye ponyatiya luchshe vsego realizovat' kak proizvodnye ot odnogo klassa. [3] Povtornoe ispol'zovanie: nuzhen mehanizm, kotoryj pozvolit prisposobit' dlya nashej biblioteki tipy, razrabotannye "gde-to v drugom meste". Konechno, vse eti otvety svyazany. V kachestve primera [2] rassmotrim ponyatie generatora iteracij. Trebuetsya opredelit' generator iteracij (v dal'nejshem iterator) dlya lyubogo tipa tak, chtoby s ego pomoshch'yu mozhno bylo porozhdat' posledovatel'nost' ob容ktov etogo tipa. Estestvenno dlya etogo nuzhno ispol'zovat' uzhe upominavshijsya klass slist. Odnako, nel'zya prosto opredelit' obshchij iterator nad slist, ili dazhe nad set, poskol'ku obshchij iterator dolzhen dopuskat' iteracii i bolee slozhnyh ob容ktov, ne yavlyayushchihsya mnozhestvami, naprimer, vhodnye potoki ili funkcii, kotorye pri ocherednom vyzove dayut sleduyushchee znachenie iteracii. Znachit nam nuzhny i mnozhestvo i iterator, i v tozhe vremya nezhelatel'no dublirovat' konkretnye tipy, kotorye yavlyayutsya ochevidnymi realizaciyami razlichnyh vidov mnozhestv i iteratorov. Mozhno graficheski predstavit' zhelatel'nuyu strukturu klassov tak: Zdes' klassy set i iter predostavlyayut interfejsy, a slist i stream yavlyayutsya chastnymi klassami i predstavlyayut realizacii. Ochevidno, nel'zya perevernut' etu ierarhiyu klassov i, predostavlyaya obshchie interfejsy, stroit' proizvodnye konkretnye tipy ot abstraktnyh klassov. V takoj ierarhii kazhdaya poleznaya operaciya nad kazhdym poleznym abstraktnym ponyatiem dolzhna predstavlyat'sya v obshchem abstraktnom bazovom klasse. Dal'nejshee obsuzhdenie etoj temy soderzhitsya v $$13.6. Privedem primer prostogo abstraktnogo tipa, yavlyayushchegosya iteratorom ob容ktov tipa T: class iter { virtual T* first() = 0; virtual T* next() = 0; virtual ~iter() { } }; class slist_iter : public iter, private slist { slink* current_elem; public: T* first(); T* next(); slist_iter() : current_elem(0) { } }; class input_iter : public iter { isstream& is; public: T* first(); T* next(); input_iter(istream& r) : is(r) { } }; Mozhno takim obrazom ispol'zovat' opredelennye nami tipy: void user(const iter& it) { for (T* p = it.first(); p; p = it.next()) { // ... } } void caller() { slist_iter sli; input_iter ii(cin); // zapolnenie sli user(sli); user(ii); } My primenili konkretnyj tip dlya realizacii abstraktnogo tipa, no mozhno ispol'zovat' ego i nezavisimo ot abstraktnyh tipov ili prosto vvodit' takie tipy dlya povysheniya effektivnosti programmy, sm. takzhe $$13.5. Krome togo, mozhno ispol'zovat' odin konkretnyj tip dlya realizacii neskol'kih abstraktnyh tipov. V razdele $$13.9 opisyvaetsya bolee gibkij iterator. Dlya nego zavisimost' ot realizacii, kotoraya postavlyaet podlezhashchie iteracii ob容kty, opredelyaetsya v moment inicializacii i mozhet izmenyat'sya v hode vypolneniya programmy. 13.4 Uzlovye klassy V dejstvitel'nosti ierarhiya klassov stroitsya, ishodya iz sovsem drugoj koncepcii proizvodnyh klassov, chem koncepciya interfejs-realizaciya, kotoraya ispol'zovalas' dlya abstraktnyh tipov. Klass rassmatrivaetsya kak fundament stroeniya. No dazhe, esli v osnovanii nahoditsya abstraktnyj klass, on dopuskaet nekotoroe predstavlenie v programme i sam predostavlyaet dlya proizvodnyh klassov kakie-to poleznye funkcii. Primerami uzlovyh klassov mogut sluzhit' klassy rectangle ($$6.4.2) i satellite ($$6.5.1). Obychno v ierarhii klass predstavlyaet nekotoroe obshchee ponyatie, a proizvodnye klassy predstavlyayut konkretnye varianty etogo ponyatiya. Uzlovoj klass yavlyaetsya neot容mlemoj chast'yu ierarhii klassov. On pol'zuetsya servisom, predstavlyaemym bazovymi klassami, sam obespechivaet opredelennyj servis i predostavlyaet virtual'nye funkcii i (ili) zashchishchennyj interfejs, chtoby pozvolit' dal'nejshuyu detalizaciyu svoih operacij v proizvodnyh klassah. Tipichnyj uzlovoj klass ne tol'ko predostavlyaet realizaciyu interfejsa, zadavaemogo ego bazovym klassom (kak eto delaet klass realizacii po otnosheniyu k abstraktnomu tipu), no i sam rasshiryaet interfejs, dobavlyaya novye funkcii. Rassmotrim v kachestve primera klass dialog_box, kotoryj predstavlyaet okno nekotorogo vida na ekrane. V etom okne poyavlyayutsya voprosy pol'zovatelyu i v nem on zadaet svoj otvet s pomoshch'yu nazhatiya klavishi ili "myshi": class dialog_box : public window { // ... public: dialog_box(const char* ...); // zakanchivayushchijsya nulem spisok // oboznachenij klavish // ... virtual int ask(); }; Zdes' vazhnuyu rol' igraet funkciya ask() i konstruktor, s pomoshch'yu kotorogo programmist ukazyvaet ispol'zuemye klavishi i zadaet ih chislovye znacheniya. Funkciya ask() izobrazhaet na ekrane okno i vozvrashchaet nomer nazhatoj v otvet klavishi. Mozhno predstavit' takoj variant ispol'zovaniya: void user() { for (;;) { // kakie-to komandy dialog_box cont("continue", "try again", "abort", (char*) 0); switch (cont.ask()) { case 0: return; case 1: break; case 2: abort(); } } } Obratim vnimanie na ispol'zovanie konstruktora. Konstruktor, kak pravilo, nuzhen dlya uzlovogo klassa i chasto eto netrivial'nyj konstruktor. |tim uzlovye klassy otlichayutsya ot abstraktnyh klassov, dlya kotoryh redko nuzhny konstruktory. Pol'zovatel' klassa dialog_box ( a ne tol'ko sozdatel' etogo klassa) rasschityvaet na servis, predstavlyaemyj ego bazovymi klassami. V rassmatrivaemom primere predpolagaetsya, chto sushchestvuet nekotoroe standartnoe razmeshchenie novogo okna na ekrane. Esli pol'zovatel' zahochet upravlyat' razmeshcheniem okna, bazovyj dlya dialog_box klass window (okno) dolzhen predostavlyat' takuyu vozmozhnost', naprimer: dialog_box cont("continue","try again","abort",(char*)0); cont.move(some_point); Zdes' funkciya dvizheniya okna move() rasschityvaet na opredelennye funkcii bazovyh klassov. Sam klass dialog_box yavlyaetsya horoshim kandidatom dlya postroeniya proizvodnyh klassov. Naprimer, vpolne razumno imet' takoe okno, v kotorom, krome nazhatiya klavishi ili vvoda s mysh'yu, mozhno zadavat' stroku simvolov (skazhem, imya fajla). Takoe okno dbox_w_str stroitsya kak proizvodnyj klass ot prostogo okna dialog_box: class dbox_w_str : public dialog_box { // ... public: dbox_w_str ( const char* sl, // stroka zaprosa pol'zovatelyu const char* ... // spisok oboznachenij klavish ); int ask(); virtual char* get_string(); //... }; Funkciya get_string() yavlyaetsya toj operaciej, s pomoshch'yu kotoroj programmist poluchaet zadannuyu pol'zovatelem stroku. Funkciya ask() iz klassa dbox_w_str garantiruet, chto stroka vvedena pravil'no, a esli pol'zovatel' ne stal vvodit' stroku, to togda v programmu vozvrashchaetsya sootvetstvuyushchee znachenie (0). void user2() { // ... dbox_w_str file_name("please enter file name", "done", (char*)0); file_name.ask(); char* p = file_name.get_string(); if (p) { // ispol'zuem imya fajla } else { // imya fajla ne zadano } // } Podvedem itog - uzlovoj klass dolzhen: [1] rasschityvat' na svoi bazovye klassy kak dlya ih realizacii, tak i dlya predstavleniya servisa pol'zovatelyam etih klassov; [2] predstavlyat' bolee polnyj interfejs (t.e. interfejs s bol'shim chislom funkcij-chlenov) pol'zovatelyam, chem bazovye klassy; [3] osnovyvat' v pervuyu ochered' (no ne isklyuchitel'no) svoj obshchij interfejs na virtual'nyh funkciyah; [4] zaviset' ot vseh svoih (pryamyh i kosvennyh) bazovyh klassov; [5] imet' smysl tol'ko v kontekste svoih bazovyh klassov; [6] sluzhit' bazovym klassom dlya postroeniya proizvodnyh klassov; [7] voploshchat'sya v ob容kte. Ne vse, no mnogie, uzlovye klassy budut udovletvoryat' usloviyam 1, 2, 6 i 7. Klass, kotoryj ne udovletvoryaet usloviyu 6, pohodit na konkretnyj tip i mozhet byt' nazvan konkretnym uzlovym klassom. Klass, kotoryj ne udovletvoryaet usloviyu 7, pohodit na abstraktnyj tip i mozhet byt' nazvan abstraktnym uzlovym klassom. U mnogih uzlovyh klassov est' zashchishchennye chleny, chtoby predostavit' dlya proizvodnyh klassov menee ogranichennyj interfejs. Ukazhem na sledstvie usloviya 4: dlya translyacii svoej programmy pol'zovatel' uzlovogo klassa dolzhen vklyuchit' opisaniya vseh ego pryamyh i kosvennyh bazovyh klassov, a takzhe opisaniya vseh teh klassov, ot kotoryh, v svoyu ochered', zavisyat bazovye klassy. V etom uzlovoj klass opyat' predstavlyaet kontrast s abstraktnym tipom. Pol'zovatel' abstraktnogo tipa ne zavisit ot vseh klassov, ispol'zuyushchihsya dlya realizacii tipa i dlya translyacii svoej programmy ne dolzhen vklyuchat' ih opisaniya. 13.5 Dinamicheskaya informaciya o tipe Inogda byvaet polezno znat' istinnyj tip ob容kta do ego ispol'zovaniya v kakih-libo operaciyah. Rassmotrim funkciyu my(set&) iz $$13.3. void my_set(set& s) { for ( T* p = s.first(); p; p = s.next()) { // moj kod } // ... } Ona horosha v obshchem sluchae, no predstavim,- stalo izvestno, chto mnogie parametry mnozhestva predstavlyayut soboj ob容kty tipa slist. Vozmozhno takzhe stal izvesten algoritm perebora elementov, kotoryj znachitel'no effektivnee dlya spiskov, chem dlya proizvol'nyh mnozhestv. V rezul'tate eksperimenta udalos' vyyasnit', chto imenno etot perebor yavlyaetsya uzkim mestom v sisteme. Togda, konechno, imeet smysl uchest' v programme otdel'no variant s slist. Dopustiv vozmozhnost' opredeleniya istinnogo tipa parametra, zadayushchego mnozhestvo, funkciyu my(set&) mozhno zapisat' tak: void my(set& s) { if (ref_type_info(s) == static_type_info(slist_set)) { // sravnenie dvuh predstavlenij tipa // s tipa slist slist& sl = (slist&)s; for (T* p = sl.first(); p; p = sl.next()) { // effektivnyj variant v raschete na list } } else { for ( T* p = s.first(); p; p = s.next()) { // obychnyj variant dlya proizvol'nogo mnozhestva } } // ... } Kak tol'ko stal izvesten konkretnyj tip slist, stali dostupny opredelennye operacii so spiskami, i dazhe stala vozmozhna realizaciya osnovnyh operacij podstanovkoj. Privedennyj variant funkcii dejstvuet otlichno, poskol'ku slist - eto konkretnyj klass, i dejstvitel'no imeet smysl otdel'no razbirat' variant, kogda parametr yavlyaetsya slist_set. Rassmotrim teper' takuyu situaciyu, kogda zhelatel'no otdel'no razbirat' variant kak dlya klassa, tak i dlya vseh ego proizvodnyh klassov. Dopustim, my imeem klass dialog_box iz $$13.4 i hotim uznat', yavlyaetsya li on klassom dbox_w_str. Poskol'ku mozhet sushchestvovat' mnogo proizvodnyh klassov ot dbox_w_str, prostuyu proverku na sovpadenie s nim nel'zya schitat' horoshim resheniem. Dejstvitel'no, proizvodnye klassy mogut predstavlyat' samye raznye varianty zaprosa stroki. Naprimer, odin proizvodnyj ot dbox_w_str klass mozhet predlagat' pol'zovatelyu varianty strok na vybor, drugoj mozhet obespechit' poisk v kataloge i t.d. Znachit, nuzhno proveryat' i na sovpadenie so vsemi proizvodnymi ot dbox_w_str klassami. |to tak zhe tipichno dlya uzlovyh klassov, kak proverka na vpolne opredelennyj tip tipichna dlya abstraktnyh klassov, realizuemyh konkretnymi tipami. void f(dialog_box& db) { dbox_w_str* dbws = ptr_cast(dbox_w_str, &db); if (dbws) { // dbox_w_str // zdes' mozhno ispol'zovat' dbox_w_str::get_string() } else { // ``obychnyj'' dialog_box } // ... } Zdes' "operaciya" privedeniya ptr_cast() svoj vtoroj parametr (ukazatel') privodit k svoemu pervomu parametru (tipu) pri uslovii, chto ukazatel' nastroen na ob容kt tip, kotorogo sovpadaet s zadannym (ili yavlyaetsya proizvodnym klassom ot zadannogo tipa). Dlya proverki tipa dialog_box ispol'zuetsya ukazatel', chtoby posle privedeniya ego mozhno bylo sravnit' s nulem. Vozmozhno al'ternativnoe reshenie s pomoshch'yu ssylki na dialog_box: void g(dialog_box& db) { try { dbox_w_str& dbws = ref_cast(dialog_box,db); // zdes' mozhno ispol'zovat' dbox_w_str::get_string() } catch (Bad_cast) { // ``obychnyj'' dialog_box } // ... } Poskol'ku net priemlemogo predstavleniya nulevoj ssylki, s kotoroj mozhno sravnivat', ispol'zuetsya osobaya situaciya, oboznachayushchaya oshibku privedeniya (t.e. sluchaj, kogda tip ne est' dbox_w_str). Inogda luchshe izbegat' sravneniya s rezul'tatom privedeniya. Razlichie funkcij ref_cast() i ptr_cast() sluzhit horoshej illyustraciej razlichij mezhdu ssylkami i ukazatelyami: ssylka obyazatel'no ssylaetsya na ob容kt, togda kak ukazatel' mozhet i ne ssylat'sya, poetomu dlya ukazatelya chasto nuzhna proverka. 13.5.1 Informaciya o tipe V S++ net inogo standartnogo sredstva polucheniya dinamicheskoj informacii o tipe, krome vyzovov virtual'nyh funkcijX. X Hotya bylo sdelano neskol'ko predlozhenij po rasshireniyu S++ v etom napravlenii. Smodelirovat' takoe sredstvo dovol'no prosto i v bol'shinstve bol'shih bibliotek est' vozmozhnosti dinamicheskih zaprosov o tipe. Zdes' predlagaetsya reshenie, obladayushchee tem poleznym svojstvom, chto ob容m informacii o tipe mozhno proizvol'no rasshiryat'. Ego mozhno realizovat' s pomoshch'yu vyzovov virtual'nyh funkcij, i ono mozhet vhodit' v rasshirennye realizacii S++. Dostatochno udobnyj interfejs s lyub