ki operacii ->, to preimushchestvo, chto teper' mozhno stroit' proizvodnye ot set_controller klassy. K sozhaleniyu, my mozhem poteryat' i nekotorye dostoinstva upravlyayushchego klassa, esli k proizvodnym klassam budut dobavlyat'sya chleny, predstavlyayushchie dannye. Mozhno skazat', chto programmnyj ob容m, kotoryj razdelyaetsya mezhdu upravlyaemymi klassami umen'shaetsya po mere rosta programmnogo ob容ma upravlyayushchego klassa. 13.10 Upravlenie pamyat'yu Pri proektirovanii biblioteki ili prosto programmy s bol'shim vremenem scheta odin iz klyuchevyh voprosov svyazan s upravleniem pamyat'yu. V obshchem sluchae sozdatel' biblioteki ne znaet, v kakom okruzhenii ona budet rabotat'. Budet li tam resurs pamyati nastol'ko kritichen, chto ee nehvatka stanet ser'eznoj problemoj, ili zhe ser'eznoj pomehoj stanut nakladnye rashody, svyazannye s upravleniem pamyat'yu? Odin iz osnovnyh voprosov upravleniya pamyat'yu mozhno sformulirovat' tak: esli funkciya f() peredaet ili vozvrashchaet ukazatel' na ob容kt, to kto dolzhen unichtozhat' etot ob容kt? Neobhodimo otvetit' i na svyazannyj s nim vopros: v kakoj moment ob容kt mozhet byt' unichtozhen? Otvety na eti voprosy osobenno vazhny dlya sozdatelej i pol'zovatelej takih kontejnerov, kak spiski, massivy i associativnye massivy. S tochki zreniya sozdatelya biblioteki ideal'nymi budut otvety: "Sistema" i "V tot moment, kogda ob容kt bol'she nikto ne ispol'zuet". Kogda sistema unichtozhaet ob容kt, obychno govoryat, chto ona zanimaetsya sborkoj musora, a ta chast' sistemy, kotoraya opredelyaet, chto ob容kt bol'she nikem ne ispol'zuetsya, i unichtozhaet ego, nazyvaetsya sborshchikom musora. K sozhaleniyu, ispol'zovanie sborshchika musora mozhet povlech' za soboj nakladnye rashody na vremya scheta i pamyat', preryvaniya poleznyh funkcij, opredelennuyu apparatnuyu podderzhku, trudnosti svyazyvaniya chastej programmy na raznyh yazykah ili prosto uslozhnenie sistemy. Mnogie pol'zovateli ne mogut pozvolit' sebe etogo.X X Govoryat, chto programmisty na Lispe znayut, naskol'ko vazhno upravlenie pamyat'yu, i poetomu ne mogut otdat' ego pol'zovatelyu. Programmisty na S tozhe znayut, naskol'ko vazhno upravlenie pamyat'yu, i poetomu ne mogut ostavit' ego sisteme. Poetomu v bol'shinstve programm na S++ ne prihoditsya rasschityvat' na sborshchik musora i nuzhno predlozhit' svoyu strategiyu razmeshcheniya ob容ktov v svobodnoj pamyati, ne obrashchayas' k sisteme. No realizacii S++ so sborshchikom musora vse-taki sushchestvuyut. Rassmotrim samuyu prostuyu shemu upravleniya pamyat'yu dlya programm na S++. Dlya etogo zamenim operator new() na trivial'nuyu funkciyu razmeshcheniya, a operator delete() - na pustuyu funkciyu: inline size_t align(size_t s) /* Dazhe v prostoj funkcii razmeshcheniya nuzhno vyravnivanie pamyati, chtoby na ob容kt mozhno bylo nastroit' ukazatel' proizvol'nogo tipa */ { union Word { void* p; long double d; long l; } int x = s + sizeof(Word) - 1; x -= x%sizeof(Word); return x; } static void* freep; // nastroim start na svobodnuyu pamyat' void* operator new(size_t s) // prostaya linejnaya funkciya razmeshcheniya { void* p = freep; s = align(s); freep += s; return p; } void operator delete(void*) { } // pusto Esli pamyat' beskonechna, to nashe reshenie daet sborshchik musora bez vsyakih slozhnostej i nakladnyh rashodov. Takoj podhod ne primenim dlya bibliotek, kogda zaranee neizvestno, kakim obrazom budet ispol'zovat'sya pamyat', i kogda programma, pol'zuyushchayasya bibliotekoj, budet imet' bol'shoe vremya scheta. Takoj sposob vydeleniya pamyati ideal'no podhodit dlya programm, kotorym trebuetsya ogranichennyj ob容m pamyati ili ob容m, proporcional'nyj razmeru vhodnogo potoka dannyh. 13.10.1 Sborshchik musora Sborku musora mozhno rassmatrivat' kak modelirovanie beskonechnoj pamyati na pamyati ogranichennogo razmera. Pomnya ob etom, mozhno otvetit' na tipichnyj vopros: dolzhen li sborshchik musora vyzyvat' destruktor dlya teh ob容ktov, pamyat' kotoryh on ispol'zuet? Pravil'nyj otvet - net, poskol'ku, esli razmeshchennyj v svobodnoj pamyati ob容kt ne byl udalen, to on ne budet i unichtozhen. Ishodya iz etogo, operaciyu delete mozhno rassmatrivat' kak zapros na vyzov destruktora (i eshche eto - soobshchenie sisteme, chto pamyat' ob容kta mozhno ispol'zovat'). No kak byt', esli dejstvitel'no trebuetsya unichtozhit' razmeshchennyj v svobodnoj pamyati ob容kt, kotoryj ne byl udalen? Zametim, chto dlya staticheskih i avtomaticheskih ob容ktov takoj vopros ne vstaet, - destruktory dlya nih neyavno vyzyvayutsya vsegda. Dalee, unichtozhenie ob容kta "vo vremya sborki musora" po suti yavlyaetsya operaciej s nepredskazuemym rezul'tatom. Ona mozhet sovershit'sya v lyuboe vremya mezhdu poslednim ispol'zovaniem ob容kta i "koncom programmy"X, a znachit, v kakom sostoyanii budet programma v etot moment neizvestno. X Zdes' ispol'zovany kavychki, potomu chto trudno tochno opredelit', chto takoe konec programmy. (prim. perev.) Trudno pravil'no zaprogrammirovat' takie operacii i oni ne tak polezny, kak kazhetsya. Zadachu unichtozheniya ob容ktov, esli vremya etoj operacii tochno ne zadano, mozhno reshit' s pomoshch'yu programmy obsluzhivaniya zayavok na unichtozhenie. Nazovem ee serverom zayavok. Esli ob容kt neobhodimo unichtozhit' v konce programmy, to nado zapisat' v global'nyj associativnyj massiv ego adres i ukazatel' na funkciyu "ochistki". Esli ob容kt udalen yavnoj operaciej, zayavka annuliruetsya. Pri unichtozhenii samogo servera (v konce programmy) vyzyvayutsya funkcii ochistki dlya vseh ostavshihsya zayavok. |to reshenie podhodit i dlya sborki musora, poskol'ku my rassmatrivaem ee kak modelirovanie beskonechnoj pamyati. Dlya sborshchika musora nuzhno vybrat' odno iz dvuh reshenij: libo udalyat' ob容kt, kogda edinstvennoj ostavshejsya ssylkoj na nego budet ssylka, nahodyashchayasya v massive samogo servera, libo (standartnoe reshenie) ne udalyat' ob容kt do konca programmy, poskol'ku vse-taki ssylka na nego est'. Server zayavok mozhno realizovat' kak associativnyj massiv ($$8.8): class Register { Map<void*, void (*) (void*)> m; public: insert(void* po, void(*pf)()) { m[po]=pf; } remove(void* po) { m.remove(po); } }; Register cleanup_register; Klass, postoyanno obrashchayushchijsya k serveru, mozhet vyglyadet' tak: class X { // ... static void cleanup(void*); public: X() { cleanup_register.insert(this,&cleanup); // ... } ~X() { cleanup(this); } // ... }; void X::cleanup(void* pv) { X* px = (X*)pv; cleanup_register.remove(pv); // ochistka } CHtoby v klasse Register ne imet' dela s tipami, my ispol'zovali staticheskuyu funkciyu-chlen s ukazatelem tipa void*. 13.10.2 Kontejnery i udalenie Dopustim, chto u nas net beskonechnoj pamyati i sborshchika musora. Na kakie sredstva upravleniya pamyat'yu mozhet rasschityvat' sozdatel' kontejnera, naprimer, klassa Vector? Dlya sluchaya takih prostyh elementov, kak int, ochevidno, nado prosto kopirovat' ih v kontejner. Stol' zhe ochevidno, chto dlya drugih tipov, takih, kak abstraktnyj klass Shape, v kontejnere sleduet hranit' ukazatel'. Sozdatel' biblioteki dolzhen predusmotret' oba varianta. Privedem nabrosok ochevidnogo resheniya: template<class T> Vector { T* p; int sz; public: Vector(int s) { p = new T[sz=s]; } // ... }; Esli pol'zovatel' ne budet zanosit' v kontejner vmesto ukazatelej na ob容kty sami ob容kty tipa Shape, to eto reshenie podhodit dlya oboih variantov. Vector<Shape*> vsp(200); // normal'no Vector<Shape> vs(200); // oshibka pri translyacii K schast'yu, translyator otslezhivaet popytku sozdat' massiv ob容ktov abstraktnogo bazovogo klassa Shape. Odnako, esli ispol'zuyutsya ukazateli, sozdatel' biblioteki i pol'zovatel' dolzhny dogovorit'sya, kto budet udalyat' hranimye v kontejnere ob容kty. Rassmotrim primer: void f() // protivorechivoe ispol'zovanie sredstv // upravleniya pamyat'yu { Vector<Shape*> v(10); Circle* cp = new Circle; v[0] = cp; v[1] = new Triangle; Square s; v[2] = &s; delete cp; // ne udalyaet ob容kty, na kotorye nastroeny // ukazateli, nahodyashchiesya v kontejnere } Esli ispol'zovat' realizaciyu klassa Vector iz $$1.4.3, ob容kt Triangle v etom primere navsegda ostanetsya v podveshennom sostoyanii (na nego net ukazatelej), esli tol'ko net sborshchika musora. Glavnoe v upravlenii pamyat'yu eto - eto korrektnost'. Rassmotrim takoj primer: void g() // korrektnoe ispol'zovanie sredstv upravleniya pamyat'yu { Vector<Shape*> v(10); Circle* cp = new Circle; v[0] = cp; v[1] = new Triangle; Square s; v[2] = &s; delete cp; delete v[1]; } Rassmotrim teper' takoj vektornyj klass,kotoryj sledit za udaleniem zanesennyh v nego ukazatelej: template<class T> MVector { T* p; int sz; public: MVector(int s); ~MVector(); // ... }; template<class T> MVector<T>::MVector(int s) { // proverka s p = new T[sz=s]; for (int i = 0; i<s; i++) p[i] = 0; } template<class T> MVector<T>::~MVector() { for (int i = 0; i<s; i++) delete p[i]; delete p; } Pol'zovatel' mozhet rasschityvat', chto soderzhashchiesya v MVector ukazateli budut udaleny. Otsyuda sleduet, chto posle udaleniya MVector pol'zovatel' ne dolzhen obrashchat'sya s pomoshch'yu ukazatelej k ob容ktam, zanosivshimsya v etot kontejner. V moment unichtozheniya MVector v nem ne dolzhno byt' ukazatelej na staticheskie ili avtomaticheskie ob容kty, naprimer: void h() // korrektnoe ispol'zovanie sredstv upravleniya pamyat'yu { MVector<Shape*> v(10); Circle* cp = new circle(); v[0] = cp; v[1] = new Triangle; Square s; v[2] = &s; v[2] = 0; // predotvrashchaet udalenie s // vse ostavshiesya ukazateli // avtomaticheski udalyayutsya pri vyhode } Estestvenno, takoe reshenie goditsya tol'ko dlya kontejnerov, v kotoryh ne soderzhatsya kopii ob容ktov, a dlya klassa Map ($$8.8), naprimer, ono ne goditsya. Zdes' priveden prostoj variant destruktora dlya MVector, no soderzhitsya oshibka, poskol'ku odin i tot zhe ukazatel', dvazhdy zanesennyj v kontejner, budet udalyat'sya tozhe dva raza. Postroenie i unichtozhenie takih kontejnerov, kotorye sledyat za udaleniem soderzhashchihsya v nih ob容ktah, dovol'no dorogostoyashchaya operaciya. Kopirovanie zhe etih kontejnerov sleduet zapretit' ili, po krajnej mere, sil'no ogranichit' (dejstvitel'no, kto budet otvechat' za udalenie kontejner ili ego kopiya?): template<class T> MVector { // ... private: MVector(const MVector&); //predotvrashchaet kopirovanie MVector& operator=(const MVector&); //to zhe samoe // ... }; Otsyuda sleduet, chto takie kontejnery nado peredavat' po ssylke ili ukazatelyu (esli, voobshche, eto sleduet delat'), no togda v upravlenii pamyat'yu voznikaet trudnost' drugogo roda. CHasto byvaet polezno umen'shit' chislo ukazatelej, za kotorymi dolzhen sledit' pol'zovatel'. Dejstvitel'no, namnogo proshche sledit' za 100 ob容ktami pervogo urovnya, kotorye, v svoyu ochered', upravlyayut 1000 ob容ktov nulevogo urovnya, chem neposredstvenno rabotat' s 1100 ob容ktami. Sobstvenno, privedennye v etom razdele priemy, kak i drugie priemy, ispol'zuemye dlya upravleniya pamyat'yu, svodyatsya k standartizacii i universalizacii za schet primeneniya konstruktorov i destruktorov. |to pozvolyaet svesti zadachu upravleniya pamyat'yu dlya prakticheski nevoobrazimogo chisla ob容ktov, skazhem 100 000, do vpolne upravlyaemogo chisla, skazhem 100. Mozhno li takim obrazom opredelit' klass kontejnera, chtoby programmist, sozdayushchij ob容kt tipa kontejnera, mog vybrat' strategiyu upravleniya pamyat'yu iz neskol'kih vozmozhnyh, hotya opredelen kontejner tol'ko odnogo tipa? Esli eto vozmozhno, to budet li opravdano? Na vtoroj vopros otvet polozhitel'nyj, poskol'ku bol'shinstvo funkcij v sisteme voobshche ne dolzhny zabotit'sya o raspredelenii pamyati. Sushchestvovanie zhe neskol'kih raznyh tipov dlya kazhdogo kontejnernogo klassa yavlyaetsya dlya pol'zovatelya nenuzhnym uslozhneniem. V biblioteke dolzhen byt' ili odin vid kontejnerov (Vector ili MVector), ili zhe oba, no predstavlennye kak varianty odnogo tipa, naprimer: template<class T> PVector { T** p; int sz; int managed; public: PVector(int s, int managed = 0 ); ~PVector(); // ... }; template<class T> PVector<T>::PVector(int s, int m) { // proverka s p = new T*[sz=s]; if (managed = m) for (int i = 0; i<s; i++) p[i] = 0; } template<class T> PVector<T>::~PVector() { if (managed) { for (int i = 0; i<s; i++) delete p[i]; } delete p; } Primerom klassa, kotoryj mozhet predlozhit' biblioteka dlya oblegcheniya upravleniya pamyat'yu, yavlyaetsya upravlyayushchij klass iz $$13.9. Raz v upravlyayushchem klasse vedetsya podschet ssylok na nego, mozhno spokojno peredavat' ob容kt etogo klassa, ne dumaya o tom, kto budet udalyat' dostupnye cherez nego ob容kty. |to sdelaet sam ob容kt upravlyayushchego klassa. No takoj podhod trebuet, chtoby v upravlyaemyh ob容ktah bylo pole dlya podscheta chisla ispol'zovanij. Vvedya dopolnitel'nyj ob容kt, mozhno prosto snyat' eto zhestkoe trebovanie: template<class T> class Handle { T* rep; int* pcount; public: T* operator->() { return rep; } Handle(const T* pp) : rep(pp), pcount(new int) { (*pcount) = 0; } Handle(const Handle& r) : rep(r.rep), pcount(r.count) { (*pcount)++; } void bind(const Handle& r) { if (rep == r.rep) return; if (--(*pcount) == 0) { delete rep; delete pcount; } rep = r.rep; pcount = r.pcount; (*pcount)++; } Handle& operator=(const Handle& r) { bind(r); return *this; } ~Handle() { if (--(*pcount) == 0) { delete rep; delete pcount; } } }; 13.10.3 Funkcii razmeshcheniya i osvobozhdeniya Vo vseh privedennyh primerah pamyat' rassmatrivalas' kak nechto dannoe. Odnako, obychnaya funkciya obshchego naznacheniya dlya raspredeleniya svobodnoj pamyati okazyvaetsya do udivleniya menee effektivnoj, chem funkciya razmeshcheniya special'nogo naznacheniya. Vyrozhdennym sluchaem takih funkcij mozhno schitat' privedennyj primer s razmeshcheniem v "beskonechnoj" pamyati i s pustoj funkciej osvobozhdeniya. V biblioteke mogut byt' bolee soderzhatel'nye funkcii razmeshcheniya, i byvaet, chto s ih pomoshch'yu udaetsya udvoit' skorost' vypolneniya programmy. No prezhde, chem pytat'sya s ih pomoshch'yu optimizirovat' programmu, zapustite dlya nee profilirovshchik, chtoby vyyavit' nakladnye rashody, svyazannye s vydeleniem pamyati. V razdelah $$5.5.6 i $$6.7 bylo pokazano kak s pomoshch'yu opredeleniya funkcij X::operator new() i X::operator delete() mozhno ispol'zovat' funkciyu razmeshcheniya dlya ob容ktov klassa X. Zdes' est' opredelennaya trudnost'. Dlya dvuh klassov X i Y funkcii razmeshcheniya mogut byt' nastol'ko shodnymi, chto zhelatel'no imet' odnu takuyu funkciyu. Inymi slovami, zhelatel'no imet' v biblioteke takoj klass, kotoryj predostavlyaet funkcii razmeshcheniya i osvobozhdeniya, prigodnye dlya razmeshcheniya ob容ktov dannogo klassa. Esli takoj klass est', to funkcii razmeshcheniya i osvobozhdeniya dlya dannogo klassa poluchayutsya za schet privyazki k nemu obshchih funkcij razmeshcheniya i osvobozhdeniya: class X { static Pool my_pool; // ... public: // ... void* operator new(size_t) { return my_pool.alloc(); } void operator delete(void* p) { my_pool.free(p); } }; Pool X::my_pool(sizeof(X)); S pomoshch'yu klassa Pool pamyat' raspredelyaetsya blokami odnogo razmera. V privedennom primere ob容kt my_pool otvodit pamyat' blokami razmerom sizeof(X). Sostavlyaetsya opisanie klassa X i ispol'zuetsya Pool s uchetom optimizacii skorosti programmy i kompaktnosti predstavleniya. Obratite vnimanie, chto razmer vydelyaemyh blokov pamyati yavlyaetsya dlya klassa "vstroennym", poetomu zadayushchij razmer parametr funkcii X::operator new() ne ispol'zuetsya. Ispol'zuetsya variant funkcii X::operator delete() bez parametra. Esli klass Y yavlyaetsya proizvodnym klassa X, i sizeof(Y)>sizeof(X), to dlya klassa Y dolzhny byt' svoi funkcii razmeshcheniya i osvobozhdeniya. Nasledovanie funkcij klassa X privedet k katastrofe. K schast'yu, zadat' takie funkcii dlya Y ochen' prosto. Klass Pool predostavlyaet svyazannyj spisok elementov trebuemogo razmera. |lementy vydelyayutsya iz bloka pamyati fiksirovannogo razmera i po mere nadobnosti zaprashivayutsya novye bloki pamyati. |lementy gruppiruyutsya bol'shimi blokami, chtoby minimizirovat' chislo obrashchenij za pamyat'yu k funkcii razmeshcheniya obshchego naznacheniya. Do teh por poka ne budet unichtozhen sam ob容kt PooL, pamyat' nikogda ne vozvrashchaetsya funkcii razmeshcheniya obshchego naznacheniya. Privedem opisanie klassa Pool: class Pool { struct Link { Link* next; } const unsigned esize; Link* head; Pool(Pool&); // zashchita ot kopirovaniya void operator= (Pool&); // zashchita ot kopirovaniya void grow(); public: Pool(unsigned n); ~Pool(); void* alloc(); void free(void* b); }; inline void* Pool::alloc() { if (head==0) grow(); Link* p = head; head = p->next; return p; } inline void Pool::free(void* b) { Link* p = (Link*) b; p->next = head; head = p; } Privedennye opisaniya logichno pomestit' v zagolovochnyj fajl Pool.h. Sleduyushchie opredeleniya mogut nahodit'sya v lyubom meste programme i zavershayut nash primer. Ob容kt Pool dolzhen inicializirovat'sya konstruktorom: Pool::Pool(unsigned sz) : esize(sz) { head = 0; } Funkciya Pool::grow() budet svyazyvat' vse elementy v spisok kvantov svobodnoj pamyati head, obrazuya iz nih novyj blok. Opredeleniya ostal'nyh funkcij-chlenov ostavleny v kachestve uprazhnenij 5 i 6 v $$13.11. void Pool::grow() { const int overhead = 12; const int chunk_size = 8*1024-overhead; const int nelem = (chunk_size-esize)/esize; char* start = new char[chunk_size]; char* last = &start[(nelem-1)*esize]; for (char* p = start; p<last; p+=esize) ((Link*)p)->next = ((Link*)p)+1; ((Link*)last)->next = 0; head = (Link*)start; } 13.11 Uprazhneniya 1. (*3) Zavershite opredeleniya funkcij-chlenov klassa Type_info. 2. (*3) Predlozhite takuyu strukturu ob容kta Type_info, chtoby funkciya Type_info::get_info() stala lishnej, i perepishite s uchetom etogo funkcii-chleny Type_info. 3. (*2.5) Naskol'ko naglyadno vy smozhete zapisat' primery s Dialog_box, ne ispol'zuya makroopredeleniya (a takzhe rasshireniya yazyka)? Naskol'ko naglyadno vam udastsya zapisat' ih, ispol'zuya rasshireniya yazyka? 4. (*4) Issledujte dve shiroko rasprostranennye biblioteki. Klassificirujte vse bibliotechnye klassy, razbiv ih na: konkretnye tipy, abstraktnye tipy, uzlovye klassy, upravlyayushchie klassy i interfejsnye klassy. Ispol'zuyutsya li abstraktnye uzlovye klassy i konkretnye uzlovye klassy? Mozhno li predlozhit' bolee podhodyashchee razbienie klassov etih bibliotek? Ispol'zuetsya li obshirnyj interfejs? Kakie imeyutsya sredstva dinamicheskoj informacii o tipe (esli oni est')? Kakova strategiya upravleniya pamyat'yu? 5. (*3) Opredelite shablonnyj variant klassa Pool iz $$13.10.3. Pust' razmer vydelyaemogo elementa pamyati budet parametrom shablona tipa, a ne konstruktora. 6. (*2.5) Usovershenstvujte shablon tipa Pool iz predydushchego uprazhneniya tak, chtoby nekotorye elementy razmeshchalis' vo vremya raboty konstruktora. Sformulirujte v chem budet problema perenosimosti, esli ispol'zovat' Pool s tipom elementov char, pokazhite kak ee ustranit'. 7. (*3) Esli vasha versiya S++ pryamo ne podderzhivaet dinamicheskie zaprosy o tipe, obratites' k svoej osnovnoj biblioteke. Realizovan li tam mehanizm dinamicheskih zaprosov o tipe? Esli eto tak, zadajte operacii iz $$13.5 kak nadstrojku nad etim mehanizmom. 8. (*2.5) Opredelite takoj strokovyj klass, v kotorom net nikakogo dinamicheskogo kontrolya, i vtoroj proizvodnyj ot nego strokovyj klass, kotoryj tol'ko provodit dinamicheskij kontrol' i obrashchaetsya k pervomu. Ukazhite plyusy i minusy takogo resheniya po sravneniyu s resheniem,v kotorom delaetsya vyborochnyj dinamicheskij kontrol', sravnite s podhodom, ispol'zuyushchim invarianty, kak bylo predlozheno v $$12.2.7.1. Naskol'ko mozhno sovmeshchat' eti podhody? 9. (*4) Opredelite klass Storable kak abstraktnyj bazovyj klass s virtual'nymi funkciyami writeout() i readin(). Dlya prostoty dopustim, chto dlya zadaniya nuzhnogo adresnogo prostranstva dostatochno stroki simvolov. S pomoshch'yu klassa Storable realizujte obmen ob容ktami s diskom. Prover'te ego na ob容ktah neskol'kih klassov po svoemu usmotreniyu. 10.(*4) Opredelite bazovyj klass Persistent s operaciyami save() i nosave(), kotoryj budet proveryat', chto destruktor sozdal ob容kt v opredelennoj pamyati. Kakie eshche poleznye operacii mozhno predlozhit'? Prover'te klass Persistent na neskol'kih klassah po svoemu vyboru. YAvlyaetsya li klass Persistent uzlovym klassom, konkretnym ili abstraktnym tipom? Argumentirujte otvet. 11.(*3) Sostav'te tol'ko opisanie klassa stack, kotoryj realizuet stek s pomoshch'yu operacij create() (sozdat' stek), delete() (unichtozhit' stek), push() (zapisat' v stek) i pop() (chitat' iz steka). Ispol'zujte tol'ko staticheskie chleny. Dlya privyazki i oboznacheniya stekov opredelite klass id. Garantirujte, chto pol'zovatel' smozhet kopirovat' ob容kty stack::id, no ne smozhet rabotat' s nimi inym sposobom. Sravnite eto opredelenie steka s klassom stack iz $$8.2. 12.(*3) Sostav'te opisanie klassa stack, kotoryj yavlyaetsya abstraktnym tipom ($$13.3). Predlozhite dve razlichnye realizacii dlya interfejsa, zadannogo stack. Napishite nebol'shuyu programmu, rabotayushchuyu s etimi klassami. Sravnite eto reshenie s klassami, opredelyayushchimi stek, iz predydushchego uprazhneniya i iz $$8.2. 13.(*3) Sostav'te takoe opisanie klassa stack, dlya kotorogo mozhno v dinamike menyat' realizaciyu. Podskazka: "Vsyakuyu zadachu mozhno reshit', vvedya eshche odnu kosvennost'". 14.(*3.5) Opredelite klass Oper, soderzhashchij identifikator (nekotorogo podhodyashchego tipa) i operaciyu (nekotoryj ukazatel' na funkciyu). Opredelite klass cat_object, soderzhashchij spisok ob容ktov Oper i ob容kt tipa void*. Zadajte v klasse cat_object operacii: add_oper(), kotoraya dobavlyaet ob容kt k spisku; remove_oper(id), kotoraya udalyaet iz spiska ob容kt Oper c identifikatorom id; operator() (id,arg), kotoraya vyzyvaet funkciyu iz ob容kta Oper c identifikatorom id. Realizujte s pomoshch'yu klassa cat_object stek ob容ktov Oper. Napishite nebol'shuyu programmu, rabotayushchuyu s etimi klassami. 15.(*3) Opredelite shablon tipa Object, sluzhashchij bazovym klassom dlya cat_object. S pomoshch'yu Object realizujte stek dlya ob容ktov klassa String. Napishite nebol'shuyu programmu, ispol'zuyushchuyu etot shablon tipa. 16.(*3) Opredelite variant klassa Object pod imenem Class, v kotorom ob容kty s odinakovym identifikatorom imeyut obshchij spisok operacij. Napishite nebol'shuyu programmu, ispol'zuyushchuyu etot shablon tipa. 17.(*3) Opredelite shablon tipa Stack, kotoryj zadaet tradicionnyj i nadezhnyj interfejs so stekom, realizuemym ob容ktom shablona tipa Object. Sravnite eto opredelenie steka s klassami, zadayushchimi stek, iz predydushchih uprazhnenij. Napishite nebol'shuyu programmu, ispol'zuyushchuyu etot shablon tipa.