to obespechivaet opisanie window vo vseh proizvodnyh klassah kak virtual'nogo bazovogo klassa. Mozhno sleduyushchim obrazom izobrazit' sostav ob容kta klassa window_w_border_and_menu: CHtoby uvidet' raznicu mezhdu obychnym i virtual'nym nasledovaniem, sravnite etot risunok s risunkom iz $$6.5, pokazyvayushchim sostav ob容kta klassa satellite. V grafe nasledovaniya kazhdyj bazovyj klass s dannym imenem, kotoryj byl ukazan kak virtual'nyj, budet predstavlen edinstvennym ob容ktom etogo klassa. Naprotiv, kazhdyj bazovyj klass, kotoryj pri opisanii nasledovaniya ne byl ukazan kak virtual'nyj, budet predstavlen svoim sobstvennym ob容ktom. Teper' nado napisat' vse eti funkcii draw(). |to ne slishkom trudno, no dlya neostorozhnogo programmista zdes' est' lovushka. Snachala pojdem samym prostym putem, kotoryj kak raz k nej i vedet: void window_w_border::draw() { window::draw(); // risuem ramku } void window_w_menu::draw() { window::draw(); // risuem menyu } Poka vse horosho. Vse eto ochevidno, i my sleduem obrazcu opredeleniya takih funkcij pri uslovii edinstvennogo nasledovaniya ($$6.2.1), kotoryj rabotal prekrasno. Odnako, v proizvodnom klasse sleduyushchego urovnya poyavlyaetsya lovushka: void window_w_border_and_menu::draw() // lovushka! { window_w_border::draw(); window_w_menu::draw(); // teper' operacii, otnosyashchiesya tol'ko // k oknu s ramkoj i menyu } Na pervyj vzglyad vse vpolne normal'no. Kak obychno, snachala vypolnyayutsya vse operacii, neobhodimye dlya bazovyh klassov, a zatem te, kotorye otnosyatsya sobstvenno k proizvodnym klassam. No v rezul'tate funkciya window::draw() budet vyzyvat'sya dvazhdy! Dlya bol'shinstva graficheskih programm eto ne prosto izlishnij vyzov, a porcha kartinki na ekrane. Obychno vtoraya vydacha na ekran zatiraet pervuyu. CHtoby izbezhat' lovushki, nado dejstvovat' ne tak pospeshno. My otdelim dejstviya, vypolnyaemye bazovym klassom, ot dejstvij, vypolnyaemyh iz bazovogo klassa. Dlya etogo v kazhdom klasse vvedem funkciyu _draw(), kotoraya vypolnyaet nuzhnye tol'ko dlya nego dejstviya, a funkciya draw() budet vypolnyat' te zhe dejstviya plyus dejstviya, nuzhnye dlya kazhdogo bazovogo klassa. Dlya klassa window izmeneniya svodyatsya k vvedeniyu izlishnej funkcii: class window { // golovnaya informaciya void _draw(); void draw(); }; Dlya proizvodnyh klassov effekt tot zhe: class window_w_border : public virtual window { // klass "okno s ramkoj" // opredeleniya, svyazannye s ramkoj void _draw(); void draw(); }; void window_w_border::draw() { window::_draw(); _draw(); // risuet ramku }; Tol'ko dlya proizvodnogo klassa sleduyushchego urovnya proyavlyaetsya otlichie funkcii, kotoroe i pozvolyaet obojti lovushku s povtornym vyzovom window::draw(), poskol'ku teper' vyzyvaetsya window::_draw() i tol'ko odin raz: class window_w_border_and_menu : public virtual window, public window_w_border, public window_w_menu { void _draw(); void draw(); }; void window_w_border_and_menu::draw() { window::_draw(); window_w_border::_draw(); window_w_menu::_draw(); _draw(); // teper' operacii, otnosyashchiesya tol'ko // k oknu s ramkoj i menyu } Ne obyazatel'no imet' obe funkcii window::draw() i window::_draw(), no nalichie ih pozvolyaet izbezhat' razlichnyh prostyh opisok. V etom primere klass window sluzhit hranilishchem obshchej dlya window_w_border i window_w_menu informacii i opredelyaet interfejs dlya obshcheniya etih dvuh klassov. Esli ispol'zuetsya edinstvennoe nasledovanie, to obshchnost' informacii v dereve klassov dostigaetsya tem, chto eta informaciya peredvigaetsya k kornyu dereva do teh por, poka ona ne stanet dostupna vsem zainteresovannym v nej uzlovym klassam. V rezul'tate legko voznikaet nepriyatnyj effekt: koren' dereva ili blizkie k nemu klassy ispol'zuyutsya kak prostranstvo global'nyh imen dlya vseh klassov dereva, a ierarhiya klassov vyrozhdaetsya v mnozhestvo nesvyazannyh ob容ktov. Sushchestvenno, chtoby v kazhdom iz klassov-brat'ev pereopredelyalis' funkcii, opredelennye v obshchem virtual'nom bazovom klasse. Takim obrazom kazhdyj iz brat'ev mozhet poluchit' svoj variant operacij, otlichnyj ot drugih. Pust' v klasse window est' obshchaya funkciya vvoda get_input(): class window { // golovnaya informaciya virtual void draw(); virtual void get_input(); }; V odnom iz proizvodnyh klassov mozhno ispol'zovat' etu funkciyu, ne zadumyvayas' o tom, gde ona opredelena: class window_w_banner : public virtual window { // klass "okno s zagolovkom" void draw(); void update_banner_text(); }; void window_w_banner::update_banner_text() { // ... get_input(); // izmenit' tekst zagolovka } V drugom proizvodnom klasse funkciyu get_input() mozhno opredelyat', ne zadumyvayas' o tom, kto ee budet ispol'zovat': class window_w_menu : public virtual window { // klass "okno s menyu" // opredeleniya, svyazannye s menyu void draw(); void get_input(); // pereopredelyaet window::get_input() }; Vse eti opredeleniya sobirayutsya vmeste v proizvodnom klasse sleduyushchego urovnya: class window_w_banner_and_menu : public virtual window, public window_w_banner, public window_w_menu { void draw(); }; Kontrol' neodnoznachnosti pozvolyaet ubedit'sya, chto v klassah-brat'yah opredeleny raznye funkcii: class window_w_input : public virtual window { // ... void draw(); void get_input(); // pereopredelyaet window::get_input }; class window_w_input_and_menu : public virtual window, public window_w_input, public window_w_menu { // oshibka: oba klassa window_w_input i // window_w_menu pereopredelyayut funkciyu // window::get_input void draw(); }; Translyator obnaruzhivaet podobnuyu oshibku, a ustranit' neodnoznachnost' mozhno obychnym sposobom: vvesti v klassy window_w_input i window_w_menu funkciyu, pereopredelyayushchuyu "funkciyu-narushitelya", i kakim-to obrazom ustranit' neodnoznachnost': class window_w_input_and_menu : public virtual window, public window_w_input, public window_w_menu { void draw(); void get_input(); }; V etom klasse window_w_input_and_menu::get_input() budet pereopredelyat' vse funkcii get_input(). Podrobno mehanizm razresheniya neodnoznachnosti opisan v $$R.10.1.1. 6.6 Kontrol' dostupa CHlen klassa mozhet byt' chastnym (private), zashchishchennym (protected) ili obshchim (public): CHastnyj chlen klassa X mogut ispol'zovat' tol'ko funkcii-chleny i druz'ya klassa X. Zashchishchennyj chlen klassa X mogut ispol'zovat' tol'ko funkcii-chleny i druz'ya klassa X, a tak zhe funkcii-chleny i druz'ya vseh proizvodnyh ot X klassov (sm. $$5.4.1). Obshchij chlen mozhno ispol'zovat' v lyuboj funkcii. |ti pravila sootvetstvuyut deleniyu obrashchayushchihsya k klassu funkcij na tri vida: funkcii, realizuyushchie klass (ego druz'ya i chleny), funkcii, realizuyushchie proizvodnyj klass (druz'ya i chleny proizvodnogo klassa) i vse ostal'nye funkcii. Kontrol' dostupa primenyaetsya edinoobrazno ko vsem imenam. Na kontrol' dostupa ne vliyaet, kakuyu imenno sushchnost' oboznachaet imya. |to oznachaet, chto chastnymi mogut byt' funkcii-chleny, konstanty i t.d. naravne s chastnymi chlenami, predstavlyayushchimi dannye: class X { private: enum { A, B }; void f(int); int a; }; void X::f(int i) { if (i<A) f(i+B); a++; } void g(X& x) { int i = X::A; // oshibka: X::A chastnyj chlen x.f(2); // oshibka: X::f chastnyj chlen x.a++; // oshibka: X::a chastnyj chlen } 6.6.1 Zashchishchennye chleny Dadim primer zashchishchennyh chlenov, vernuvshis' k klassu window iz predydushchego razdela. Zdes' funkcii _draw() prednaznachalis' tol'ko dlya ispol'zovaniya v proizvodnyh klassah, poskol'ku predostavlyali nepolnyj nabor vozmozhnostej, a poetomu ne byli dostatochny udobny i nadezhny dlya obshchego primeneniya. Oni byli kak by stroitel'nym materialom dlya bolee razvityh funkcij. S drugoj storony, funkcii draw() prednaznachalis' dlya obshchego primeneniya. |to razlichie mozhno vyrazit', razbiv interfejsy klassov window na dve chasti - zashchishchennyj interfejs i obshchij interfejs: class window { public: virtual void draw(); // ... protected: void _draw(); // drugie funkcii, sluzhashchie stroitel'nym materialom private: // predstavlenie klassa }; Takoe razbienie mozhno provodit' i v proizvodnyh klassah, takih, kak window_w_border ili window_w_menu. Prefiks _ ispol'zuetsya v imenah zashchishchennyh funkcij, yavlyayushchihsya chast'yu realizacii klassa, po obshchemu pravilu: imena, nachinayushchiesya s _ , ne dolzhny prisutstvovat' v chastyah programmy, otkrytyh dlya obshchego ispol'zovaniya. Imen, nachinayushchihsya s dvojnogo simvola podcherkivaniya, luchshe voobshche izbegat' (dazhe dlya chlenov). Vot menee praktichnyj, no bolee podrobnyj primer: class X { // po umolchaniyu chastnaya chast' klassa int priv; protected: int prot; public: int publ; void m(); }; Dlya chlena X::m dostup k chlenam klassa neogranichen: void X::m() { priv = 1; // normal'no prot = 2; // normal'no publ = 3; // normal'no } CHlen proizvodnogo klassa imeet dostup tol'ko k obshchim i zashchishchennym chlenam: class Y : public X { void mderived(); }; Y::mderived() { priv = 1; // oshibka: priv chastnyj chlen prot = 2; // normal'no: prot zashchishchennyj chlen, a // mderived() chlen proizvodnogo klassa Y publ = 3; // normal'no: publ obshchij chlen } V global'noj funkcii dostupny tol'ko obshchie chleny: void f(Y* p) { p->priv = 1; // oshibka: priv chastnyj chlen p->prot = 2; // oshibka: prot zashchishchennyj chlen, a f() // ne drug ili chlen klassov X i Y p->publ = 3; // normal'no: publ obshchij chlen } 6.6.2 Dostup k bazovym klassam Podobno chlenu bazovyj klass mozhno opisat' kak chastnyj, zashchishchennyj ili obshchij: class X { public: int a; // ... }; class Y1 : public X { }; class Y2 : protected X { }; class Y3 : private X { }; Poskol'ku X - obshchij bazovyj klass dlya Y1, v lyuboj funkcii, esli est' neobhodimost', mozhno (neyavno) preobrazovat' Y1* v X*, i pritom v nej budut dostupny obshchie chleny klassa X: void f(Y1* py1, Y2* py2, Y3* py3) { X* px = py1; // normal'no: X - obshchij bazovyj klass Y1 py1->a = 7; // normal'no px = py2; // oshibka: X - zashchishchennyj bazovyj klass Y2 py2->a = 7; // oshibka px = py3; // oshibka: X - chastnyj bazovyj klass Y3 py3->a = 7; // oshibka } Teper' pust' opisany class Y2 : protected X { }; class Z2 : public Y2 { void f(); }; Poskol'ku X - zashchishchennyj bazovyj klass Y2, tol'ko druz'ya i chleny Y2, a takzhe druz'ya i chleny lyubyh proizvodnyh ot Y2 klassov (v chastnosti Z2) mogut pri neobhodimosti preobrazovyvat' (neyavno) Y2* v X*. Krome togo oni mogut obrashchat'sya k obshchim i zashchishchennym chlenam klassa X: void Z2::f(Y1* py1, Y2* py2, Y3* py3) { X* px = py1; // normal'no: X - obshchij bazovyj klass Y1 py1->a = 7; // normal'no px = py2; // normal'no: X - zashchishchennyj bazovyj klass Y2, // a Z2 - proizvodnyj klass Y2 py2->a = 7; // normal'no px = py3; // oshibka: X - chastnyj bazovyj klass Y3 py3->a = 7; // oshibka } Nakonec, rassmotrim: class Y3 : private X { void f(); }; Poskol'ku X - chastnyj bazovyj klass Y3, tol'ko druz'ya i chleny Y3 mogut pri neobhodimosti preobrazovyvat' (neyavno) Y3* v X*. Krome togo oni mogut obrashchat'sya k obshchim i zashchishchennym chlenam klassa X: void Y3::f(Y1* py1, Y2* py2, Y3* py3) { X* px = py1; // normal'no: X - obshchij bazovyj klass Y1 py1->a = 7; // normal'no px = py2; // oshibka: X - zashchishchennyj bazovyj klass Y2 py2->a = 7; // oshibka px = py3; // normal'no: X - chastnyj bazovyj klass Y3, // a Y3::f chlen Y3 py3->a = 7; // normal'no } 6.7 Svobodnaya pamyat' Esli opredelit' funkcii operator new() i operator delete(), upravlenie pamyat'yu dlya klassa mozhno vzyat' v svoi ruki. |to takzhe mozhno, (a chasto i bolee polezno), sdelat' dlya klassa, sluzhashchego bazovym dlya mnogih proizvodnyh klassov. Dopustim, nam potrebovalis' svoi funkcii razmeshcheniya i osvobozhdeniya pamyati dlya klassa employee ($$6.2.5) i vseh ego proizvodnyh klassov: class employee { // ... public: void* operator new(size_t); void operator delete(void*, size_t); }; void* employee::operator new(size_t s) { // otvesti pamyat' v `s' bajtov // i vozvratit' ukazatel' na nee } void employee::operator delete(void* p, size_t s) { // `p' dolzhno ukazyvat' na pamyat' v `s' bajtov, // otvedennuyu funkciej employee::operator new(); // osvobodit' etu pamyat' dlya povtornogo ispol'zovaniya } Naznachenie do sej pory zagadochnogo parametra tipa size_t stanovitsya ochevidnym. |to - razmer osvobozhdaemogo ob容kta. Pri udalenii prostogo sluzhashchego etot parametr poluchaet znachenie sizeof(employee), a pri udalenii upravlyayushchego - sizeof(manager). Poetomu sobstvennye funkcii klassy dlya razmeshcheniya mogut ne hranit' razmer kazhdogo razmeshchaemogo ob容kta. Konechno, oni mogut hranit' eti razmery (podobno funkciyam razmeshcheniya obshchego naznacheniya) i ignorirovat' parametr size_t v vyzove operator delete(), no togda vryad li oni budut luchshe, chem funkcii razmeshcheniya i osvobozhdeniya obshchego naznacheniya. Kak translyator opredelyaet nuzhnyj razmer, kotoryj nado peredat' funkcii operator delete()? Poka tip, ukazannyj v operator delete(), sootvetstvuet istinnomu tipu ob容kta, vse prosto; no rassmotrim takoj primer: class manager : public employee { int level; // ... }; void f() { employee* p = new manager; // problema delete p; } V etom sluchae translyator ne smozhet pravil'no opredelit' razmer. Kak i v sluchae udaleniya massiva, nuzhna pomoshch' programmista. On dolzhen opredelit' virtual'nyj destruktor v bazovom klasse employee: class employee { // ... public: // ... void* operator new(size_t); void operator delete(void*, size_t); virtual ~employee(); }; Dazhe pustoj destruktor reshit nashu problemu: employee::~employee() { } Teper' osvobozhdenie pamyati budet proishodit' v destruktore (a v nem razmer izvesten), a lyuboj proizvodnyj ot employee klass takzhe budet vynuzhden opredelyat' svoj destruktor (tem samym budet ustanovlen nuzhnyj razmer), esli tol'ko pol'zovatel' sam ne opredelit ego. Teper' sleduyushchij primer projdet pravil'no: void f() { employee* p = new manager; // teper' bez problem delete p; } Razmeshchenie proishodit s pomoshch'yu (sozdannogo translyatorom) vyzova employee::operator new(sizeof(manager)) a osvobozhdenie s pomoshch'yu vyzova employee::operator delete(p,sizeof(manager)) Inymi slovami, esli nuzhno imet' korrektnye funkcii razmeshcheniya i osvobozhdeniya dlya proizvodnyh klassov, nado libo opredelit' virtual'nyj destruktor v bazovom klasse, libo ne ispol'zovat' v funkcii osvobozhdeniya parametr size_t. Konechno, mozhno bylo pri proektirovanii yazyka predusmotret' sredstva, osvobozhdayushchie pol'zovatelya ot etoj problemy. No togda pol'zovatel' "osvobodilsya" by i ot opredelennyh preimushchestv bolee optimal'noj, hotya i menee nadezhnoj sistemy. V obshchem sluchae, vsegda est' smysl opredelyat' virtual'nyj destruktor dlya vseh klassov, kotorye dejstvitel'no ispol'zuyutsya kak bazovye, t.e. s ob容ktami proizvodnyh klassov rabotayut i, vozmozhno, udalyayut ih, cherez ukazatel' na bazovyj klass: class X { // ... public: // ... virtual void f(); // v X est' virtual'naya funkciya, poetomu // opredelyaem virtual'nyj destruktor virtual ~X(); }; 6.7.1 Virtual'nye konstruktory Uznav o virtual'nyh destruktorah, estestvenno sprosit': "Mogut li konstruktory to zhe byt' virtual'nymi?" Esli otvetit' korotko - net. Mozhno dat' bolee dlinnyj otvet: "Net, no mozhno legko poluchit' trebuemyj effekt". Konstruktor ne mozhet byt' virtual'nym, poskol'ku dlya pravil'nogo postroeniya ob容kta on dolzhen znat' ego istinnyj tip. Bolee togo, konstruktor - ne sovsem obychnaya funkciya. On mozhet vzaimodejstvovat' s funkciyami upravleniya pamyat'yu, chto nevozmozhno dlya obychnyh funkcij. Ot obychnyh funkcij-chlenov on otlichaetsya eshche tem, chto ne vyzyvaetsya dlya sushchestvuyushchih ob容ktov. Sledovatel'no nel'zya poluchit' ukazatel' na konstruktor. No eti ogranicheniya mozhno obojti, esli opredelit' funkciyu, soderzhashchuyu vyzov konstruktora i vozvrashchayushchuyu postroennyj ob容kt. |to udachno, poskol'ku neredko byvaet nuzhno sozdat' novyj ob容kt, ne znaya ego istinnogo tipa. Naprimer, pri translyacii inogda voznikaet neobhodimost' sdelat' kopiyu dereva, predstavlyayushchego razbiraemoe vyrazhenie. V dereve mogut byt' uzly vyrazhenij raznyh vidov. Dopustim, chto uzly, kotorye soderzhat povtoryayushchiesya v vyrazhenii operacii, nuzhno kopirovat' tol'ko odin raz. Togda nam potrebuetsya virtual'naya funkciya razmnozheniya dlya uzla vyrazheniya. Kak pravilo "virtual'nye konstruktory" yavlyayutsya standartnymi konstruktorami bez parametrov ili konstruktorami kopirovaniya, parametrom kotoryh sluzhit tip rezul'tata: class expr { // ... public: expr(); // standartnyj konstruktor virtual expr* new_expr() { return new expr(); } }; Virtual'naya funkciya new_expr() prosto vozvrashchaet standartno inicializirovannyj ob容kt tipa expr, razmeshchennyj v svobodnoj pamyati. V proizvodnom klasse mozhno pereopredelit' funkciyu new_expr() tak, chtoby ona vozvrashchala ob容kt etogo klassa: class conditional : public expr { // ... public: conditional(); // standartnyj konstruktor expr* new_expr() { return new conditional(); } }; |to oznachaet, chto, imeya ob容kt klassa expr, pol'zovatel' mozhet sozdat' ob容kt v "tochnosti takogo zhe tipa": void user(expr* p1, expr* p2) { expr* p3 = p1->new_expr(); expr* p4 = p2->new_expr(); // ... } Peremennym p3 i p4 prisvaivayutsya ukazateli neizvestnogo, no podhodyashchego tipa. Tem zhe sposobom mozhno opredelit' virtual'nyj konstruktor kopirovaniya, nazyvaemyj operaciej razmnozheniya, no nado podojti bolee tshchatel'no k specifike operacii kopirovaniya: class expr { // ... expr* left; expr* right; public: // ... // kopirovat' `s' v `this' inline void copy(expr* s); // sozdat' kopiyu ob容kta, na kotoryj smotrit this virtual expr* clone(int deep = 0); }; Parametr deep pokazyvaet razlichie mezhdu kopirovaniem sobstvenno ob容kta (poverhnostnoe kopirovanie) i kopirovaniem vsego poddereva, kornem kotorogo sluzhit ob容kt (glubokoe kopirovanie). Standartnoe znachenie 0 oznachaet poverhnostnoe kopirovanie. Funkciyu clone() mozhno ispol'zovat', naprimer, tak: void fct(expr* root) { expr* c1 = root->clone(1); // glubokoe kopirovanie expr* c2 = root->clone(); // poverhnostnoe kopirovanie // ... } YAvlyayas' virtual'noj, funkciya clone() sposobna razmnozhat' ob容kty lyubogo proizvodnogo ot expr klassa. Nastoyashchee kopirovanie mozhno opredelit' tak: void expr::copy(expression* s, int deep) { if (deep == 0) { // kopiruem tol'ko chleny *this = *s; } else { // projdemsya po ukazatelyam: left = s->clone(1); right = s->clone(1); // ... } } Funkciya expr::clone() budet vyzyvat'sya tol'ko dlya ob容ktov tipa expr (no ne dlya proizvodnyh ot expr klassov), poetomu mozhno prosto razmestit' v nej i vozvratit' iz nee ob容kt tipa expr, yavlyayushchijsya sobstvennoj kopiej: expr* expr::clone(int deep) { expr* r = new expr(); // stroim standartnoe vyrazhenie r->copy(this,deep); // kopiruem `*this' v `r' return r; } Takuyu funkciyu clone() mozhno ispol'zovat' dlya proizvodnyh ot expr klassov, esli v nih ne poyavlyayutsya chleny-dannye (a eto kak raz tipichnyj sluchaj): class arithmetic : public expr { // ... // novyh chlenov-dannyh net => // mozhno ispol'zovat' uzhe opredelennuyu funkciyu clone }; S drugoj storony, esli dobavleny chleny-dannye, to nuzhno opredelyat' sobstvennuyu funkciyu clone(): class conditional : public expression { expr* cond; public: inline void copy(cond* s, int deep = 0); expr* clone(int deep = 0); // ... }; Funkcii copy() i clone() opredelyayutsya podobno svoim dvojnikam iz expression: expr* conditional::clone(int deep) { conditional* r = new conditional(); r->copy(this,deep); return r; } void conditional::copy(expr* s, int deep) { if (deep == 0) { *this = *s; } else { expr::copy(s,1); // kopiruem chast' expr cond = s->cond->clone(1); } } Opredelenie poslednej funkcii pokazyvaet otlichie nastoyashchego kopirovaniya v expr::copy() ot polnogo razmnozheniya v expr::clone() (t.e. sozdaniya novogo ob容kta i kopirovaniya v nego). Prostoe kopirovanie okazyvaetsya poleznym dlya opredeleniya bolee slozhnyh operacij kopirovaniya i razmnozheniya. Razlichie mezhdu copy() i clone() ekvivalentno razlichiyu mezhdu operaciej prisvaivaniya i konstruktorom kopirovaniya ($$1.4.2) i ekvivalentno razlichiyu mezhdu funkciyami _draw() i draw() ($$6.5.3). Otmetim, chto funkciya copy() ne yavlyaetsya virtual'noj. Ej i ne nado byt' takovoj, poskol'ku virtual'na vyzyvayushchaya ee funkciya clone(). Ochevidno, chto prostye operacii kopirovaniya mozhno takzhe opredelyat' kak funkcii-podstanovki. 6.7.2 Ukazanie razmeshcheniya Po umolchaniyu operaciya new sozdaet ukazannyj ej ob容kt v svobodnoj pamyati. Kak byt', esli nado razmestit' ob容kt v opredelennom meste? |togo mozhno dobit'sya pereopredeleniem operacii razmeshcheniya. Rassmotrim prostoj klass: class X { // ... public: X(int); // ... }; Ob容kt mozhno razmestit' v lyubom meste, esli vvesti v funkciyu razmeshcheniya dopolnitel'nye parametry: // operaciya razmeshcheniya v ukazannom meste: void* operator new(size_t, void* p) { return p; } i zadav eti parametry dlya operacii new sleduyushchim obrazom: char buffer[sizeof(X)]; void f(int i) { X* p = new(buffer) X(i); // razmestit' X v buffer // ... } Funkciya operator new(), ispol'zuemaya operaciej new, vybiraetsya soglasno pravilam sopostavleniya parametrov ($$R.13.2). Vse funkcii operator new() dolzhny imet' pervym parametrom size_t. Zadavaemyj etim parametrom razmer neyavno peredaetsya operaciej new. Opredelennaya nami funkciya operator new() s zadavaemym razmeshcheniem yavlyaetsya samoj prostoj iz funkcij podobnogo roda. Mozhno privesti drugoj primer funkcii razmeshcheniya, vydelyayushchej pamyat' iz nekotoroj zadannoj oblasti: class Arena { // ... virtual void* alloc(size_t) = 0; virtual void free(void*) = 0; }; void operator new(size_t sz, Arena* a) { return a.alloc(sz); } Teper' mozhno otvodit' pamyat' dlya ob容ktov proizvol'nyh tipov iz razlichnyh oblastej (Arena): extern Arena* Persistent; // postoyannaya pamyat' extern Arena* Shared; // razdelyaemaya pamyat' void g(int i) { X* p = new(Persistent) X(i); // X v postoyannoj pamyati X* q = new(Shared) X(i); // X v razdelyaemoj pamyati // ... } Esli my pomeshchaem ob容kt v oblast' pamyati, kotoraya neposredstvenno ne upravlyaetsya standartnymi funkciyami raspredeleniya svobodnoj pamyati, to nado pozabotit'sya o pravil'nom unichtozhenii ob容kta. Osnovnym sredstvom zdes' yavlyaetsya yavnyj vyzov destruktora: void h(X* p) { p->~X(); // vyzov destruktora Persistent->free(p); // osvobozhdenie pamyati } Zametim, chto yavnyh vyzovov destruktorov, kak i global'nyh funkcij razmeshcheniya special'nogo naznacheniya, sleduet, po vozmozhnosti, izbegat'. Byvayut sluchai, kogda obojtis' bez nih trudno, no novichok dolzhen trizhdy podumat', prezhde chem ispol'zovat' yavnyj vyzov destruktora, i dolzhen snachala posovetovat'sya s bolee opytnym kollegoj. 6.8 Uprazhneniya 1. (*1) Pust' est' klass class base { public: virtual void iam() { cout << "base\n"; } }; Opredelite dva proizvodnyh ot base klassa i v kazhdom opredelite funkciyu iam(), vydayushchuyu imya svoego klassa. Sozdajte ob容kty etih klassov i vyzovite iam() dlya nih. Prisvojte adresa ob容ktov proizvodnyh klassov ukazatelyu tipa base* i vyzovite iam() s pomoshch'yu etih ukazatelej. 2. (*2) Realizujte primitivy upravleniya ekranom ($$6.4.1) razumnym dlya vashej sistemy obrazom. 3. (*2) Opredelite klassy triangle (treugol'nik) i circle (okruzhnost'). 4. (*2) Opredelite funkciyu, risuyushchuyu otrezok pryamoj, soedinyayushchij dve figury. Vnachale nado najti samye blizhajshie tochki figur, a zatem soedinit' ih. 5. (*2) Izmenite primer s klassom shape tak, chtoby line bylo proizvodnym klassom ot rectangle, ili naoborot. 6. (*2) Pust' est' klass class char_vec { int sz; char element [1]; public: static new_char_vec(int s); char& operator[] (int i) { return element[i]; } // ... }; Opredelite funkciyu new_char_vec() dlya otvedeniya nepreryvnogo uchastka pamyati dlya ob容ktov char_vec tak, chtoby elementy mozhno bylo indeksirovat' kak massiv element[]. V kakom sluchae eta funkciya vyzovet ser'eznye trudnosti? 7. (*1) Opishite struktury dannyh, kotorye nuzhny dlya primera s klassom shape iz $$6.4, i ob座asnite, kak mozhet vypolnyat'sya virtual'nyj vyzov. 8. (*1.5) Opishite struktury dannyh, kotorye nuzhny dlya primera s klassom satellite iz $$6.5, i ob座asnite, kak mozhet vypolnyat'sya virtual'nyj vyzov. 9. (*2) Opishite struktury dannyh, kotorye nuzhny dlya primera s klassom window iz $$6.5.3, i ob座asnite, kak mozhet vypolnyat'sya virtual'nyj vyzov. 10. (*2) Opishite klass graficheskih ob容ktov s naborom vozmozhnyh operacij, kotoryj budet obshchim bazovym v biblioteke graficheskih ob容ktov. Issledujte kakie-nibud' graficheskie biblioteki, chtoby ponyat', kakie operacii nuzhny. Opredelite klass ob容ktov bazy dannyh s naborom vozmozhnyh operacij, kotoryj budet obshchim bazovym klassom ob容ktov, hranyashchihsya kak posledovatel'nost' polej bazy dannyh. Issledujte kakie-nibud' bazy dannyh, chtoby ponyat', kakie operacii nuzhny. Opredelite ob容kt graficheskoj bazy dannyh, ispol'zuya ili ne ispol'zuya mnozhestvennoe nasledovanie. Obsudite otnositel'nye plyusy i minusy oboih reshenij. 11. (*2) Napishite variant funkcii clone() iz $$6.7.1, v kotorom razmnozhaemyj ob容kt mozhet pomeshchat'sya v oblast' Arena ($$6.7.2), peredavaemuyu kak parametr. Realizujte prostoj klass Arena kak proizvodnyj ot Arena. 12. (*2) Pust' est' klassy Circle (okruzhnost'), Square (kvadrat) i Triangle (treugol'nik), proizvodnye ot klassa shape. Opredelite funkciyu intersect() s dvumya parametrami tipa Shape*, kotoraya vyzyvaet podhodyashchuyu funkciyu, chtoby vyyasnit', peresekayutsya li zadannye dve figury. Dlya etogo v ukazannyh klassah nuzhno opredelit' sootvetstvuyushchie virtual'nye funkcii. Ne trat'te sily na funkciyu, kotoraya dejstvitel'no ustanavlivaet, chto figury peresekayutsya, dobejtes' tol'ko pravil'noj posledovatel'nosti vyzovov funkcij. 13. (*5) Razrabotajte i realizujte biblioteku dlya modelirovaniya, upravlyaemogo sobytiyami. Podskazka: ispol'zujte <task.h>. Tam uzhe ustarevshie funkcii i mozhno napisat' luchshe. Dolzhen byt' klass task (zadacha). Ob容kt task dolzhen umet' sohranyat' svoe sostoyanie i vosstanavlivat' ego (dlya etogo mozhno opredelit' funkcii task::save() i task::restore()) i togda on mozhet dejstvovat' kak soprogramma. Special'nye zadachi mozhno opredelyat' kak ob容kty klassov, proizvodnyh ot task. Programmu, kotoruyu vypolnyaet zadacha, opredelite kak virtual'nuyu funkciyu. Dolzhna byt' vozmozhnost' peredavat' parametry novoj zadache kak parametry ee konstruktoru ili konstruktoram. Dolzhen byt' dispetcher, kotoryj realizuet ponyatie virtual'nogo vremeni. Opredelite funkciyu task::delay(long), kotoraya budet "s容dat'" virtual'noe vremya. Vazhnyj vopros razrabotki: yavlyaetsya li dispetcher chast'yu klassa task, ili on dolzhen byt' nezavisimym? Zadachi dolzhny imet' vozmozhnost' obshcheniya drug s drugom. Dlya etoj celi razrabotajte klass queue (ochered'). Pridumajte sposob, chtoby zadacha mogla ozhidat' vhodnoj potok iz neskol'kih ocheredej. Vse dinamicheskie oshibki dolzhny obrabatyvat'sya edinoobrazno. Kak organizovat' otladku programm, napisannyh s pomoshch'yu takoj biblioteki?  * GLAVA 7 Esli ya vybirayu slovo, ono znachit tol'ko to, chto ya reshu, ni bol'she i ni men'she. - SHaltaj Boltaj Glava soderzhit opisanie mehanizma peregruzki operacij v S++. Programmist mozhet zadat' interpretaciyu operacij, kogda oni primenyayutsya k ob容ktam opredelennogo klassa. Pomimo arifmeticheskih, logicheskih i operacij otnosheniya mozhno pereopredelit' vyzov funkcij (), indeksaciyu [], kosvennoe obrashchenie ->, a takzhe prisvaivanie i inicializaciyu. Mozhno opredelit' yavnye i skrytye preobrazovaniya mezhdu pol'zovatel'skimi i osnovnymi tipami. Pokazano, kak opredelit' klass, ob容kt kotorogo mozhno kopirovat' i unichtozhat' tol'ko s pomoshch'yu special'nyh, opredelennyh pol'zovatelem funkcij. 7.1 Vvedenie Obychno v programmah ispol'zuyutsya ob容kty, yavlyayushchiesya konkretnym predstavleniem abstraktnyh ponyatij. Naprimer, v S++ tip dannyh int vmeste s operaciyami +, -, *, / i t.d. realizuet (hotya i ogranichenno) matematicheskoe ponyatie celogo. Obychno s ponyatiem svyazyvaetsya nabor dejstvij, kotorye realizuyutsya v yazyke v vide osnovnyh operacij nad ob容ktami, zadavaemyh v szhatom, udobnom i privychnom vide. K sozhaleniyu, v yazykah programmirovaniya neposredstvenno predstavlyaetsya tol'ko maloe chislo ponyatij. Tak, ponyatiya kompleksnyh chisel, algebry matric, logicheskih signalov i strok v S++ ne imeyut neposredstvennogo vyrazheniya. Vozmozhnost' zadat' predstavlenie slozhnyh ob容ktov vmeste s naborom operacij, vypolnyaemyh nad takimi ob容ktami, realizuyut v S++ klassy. Pozvolyaya programmistu opredelyat' operacii nad ob容ktami klassov, my poluchaem bolee udobnuyu i tradicionnuyu sistemu oboznachenij dlya raboty s etimi ob容ktami po sravneniyu s toj, v kotoroj vse operacii zadayutsya kak obychnye funkcii. Privedem primer: class complex { double re, im; public: complex(double r, double i) { re=r; im=i; } friend complex operator+(complex, complex); friend complex operator*(complex, complex); }; Zdes' privedena prostaya realizaciya ponyatiya kompleksnogo chisla, kogda ono predstavleno paroj chisel s plavayushchej tochkoj dvojnoj tochnosti, s kotorymi mozhno operirovat' tol'ko s pomoshch'yu operacij + i *. Interpretaciyu etih operacij zadaet programmist v opredeleniyah funkcij s imenami operator+ i operator*. Tak, esli b i c imeyut tip complex, to b+c oznachaet (po opredeleniyu) operator+(b,c). Teper' mozhno priblizit'sya k privychnoj zapisi kompleksnyh vyrazhenij: void f() { complex a = complex(1,3.1); complex b = complex(1.2,2); complex c = b; a = b+c; b = b+c*a; c = a*b+complex(1,2); } Sohranyayutsya obychnye prioritety operacij, poetomu vtoroe vyrazhenie vypolnyaetsya kak b=b+(c*a), a ne kak b=(b+c)*a. 7.2 Operatornye funkcii Mozhno opisat' funkcii, opredelyayushchie interpretaciyu sleduyushchih operacij: + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- ->* , -> [] () new delete Poslednie pyat' operacij oznachayut: kosvennoe obrashchenie ($$7.9), indeksaciyu ($$7.7), vyzov funkcii ($$7.8), razmeshchenie v svobodnoj pamyati i osvobozhdenie ($$3.2.6). Nel'zya izmenit' prioritety etih operacij, ravno kak i sintaksicheskie pravila dlya vyrazhenij. Tak, nel'zya opredelit' unarnuyu operaciyu % , takzhe kak i binarnuyu operaciyu !. Nel'zya vvesti novye leksemy dlya oboznacheniya operacij, no esli nabor operacij vas ne ustraivaet, mozhno vospol'zovat'sya privychnym oboznacheniem vyzova funkcii. Poetomu ispol'zujte pow(), a ne ** . |ti ogranicheniya mozhno schest' drakonovskimi, no bolee svobodnye pravila legko privodyat k neodnoznachnosti. Dopustim, my opredelim operaciyu ** kak vozvedenie v stepen', chto na pervyj vzglyad kazhetsya ochevidnoj i prostoj zadachej. No esli kak sleduet podumat', to voznikayut voprosy: dolzhny li operacii ** vypolnyat'sya sleva napravo (kak v Fortrane) ili sprava nalevo (kak v Algole)? Kak interpretirovat' vyrazhenie a**p kak a*(*p) ili kak (a)**(p)? Imenem operatornoj funkcii yavlyaetsya sluzhebnoe slovo operator, za kotorym idet sama operaciya, naprimer, operator<<. Operatornaya funkciya opisyvaetsya i vyzyvaetsya kak obychnaya funkciya. Ispol'zovanie simvola operacii yavlyaetsya prosto kratkoj formoj zapisi vyzova operatornoj funkcii: void f(complex a, complex b) { complex c = a + b; // kratkaya forma complex d = operator+(a,b); // yavnyj vyzov } S uchetom privedennogo opisaniya tipa complex inicializatory v etom primere yavlyayutsya ekvivalentnymi. 7.2.1 Binarnye i unarnye operacii Binarnuyu operaciyu mozhno opredelit' kak funkciyu-chlen s odnim parametrom, ili kak global'nuyu funkciyu s dvumya parametrami. Znachit, dlya lyuboj binarnoj operacii @ vyrazhenie aa @ bb interpretiruetsya libo kak aa.operator(bb), libo kak operator@(aa,bb). Esli opredeleny obe funkcii, to vybor interpretacii proishodit po pravilam sopostavleniya parametrov ($$R.13.2). Prefiksnaya ili postfiksnaya unarnaya operaciya mozhet opredelyat'sya kak funkciya-chlen bez parametrov, ili kak global'naya funkciya s odnimi parametrom. Dlya lyuboj prefiksnoj unarnoj operacii @ vyrazhenie @aa interpretiruetsya libo kak aa.operator@(), libo kak operator@(aa). Esli opredeleny obe funkcii, to vybor interpretacii proishodit po pravilam sopostavleniya parametrov ($$R.13.2). Dlya lyuboj postfiksnoj unarnoj operacii @ vyrazhenie @aa interpretiruetsya libo kak aa.operator@(int), libo kak operator@(aa,int). Podrobno eto ob座asnyaetsya v $$7.10. Esli opredeleny obe funkcii, to vybor interpretacii proishodit po pravilam sopostavleniya parametrov ($$13.2). Operaciyu mozhno opredelit' tol'ko v sootvetstvii s sintaksicheskimi pravilami, imeyushchimisya dlya nee v grammatike S++. V chastnosti, nel'zya opredelit' % kak unarnuyu operaciyu, a + kak ternarnuyu. Proillyustriruem skazannoe primerami: class X { // chleny (neyavno ispol'zuetsya ukazatel' `this'): X* operator&(); // prefiksnaya unarnaya operaciya & // (vzyatie adresa) X operator&(X); // binarnaya operaciya & (I porazryadnoe) X operator++(int); // postfiksnyj inkrement X operator&(X,X); // oshibka: & ne mozhet byt' ternarnoj X operator/(); // oshibka: / ne mozhet byt' unarnoj }; // global'nye funkcii (obychno druz'ya) X operator-(X); // prefiksnyj unarnyj minus X operator-(X,X); // binarnyj minus X operator--(X&,int); // postfiksnyj inkrement X operator-(); // oshibka: net operanda X operator-(X,X,X); // oshibka: ternarnaya operaciya X operator%(X); // oshibka: unarnaya operaciya % Operaciya [] opisyvaetsya v $$7.7, operaciya () v $$7.8, operaciya -> v $$7.9, a operacii ++ i -- v $$7.10. 7.2.2 Predopredelennye svojstva operacij Ispol'zuetsya tol'ko neskol'ko predpolozhenij o svojstvah pol'zovatel'skih operacij. V chastnosti, operator=, operator[], operator() i operator-> dolzhny byt' nestaticheskimi funkciyami-chlenami. |tim obespechivaetsya to, chto pervyj operand etih operacij yavlyaetsya adresom. Dlya nekotoryh vstroennyh operacij ih interpretaciya opredelyaetsya kak kombinaciya drugih operacij, vypolnyaemyh nad temi zhe operandami. Tak, esli a tipa int, to ++a oznachaet a+=1, chto v svoyu ochered' oznachaet a=a+1. Takie sootnosheniya ne sohranyayutsya dlya pol'zovatel'skih operacij, esli tol'ko pol'zovatel' special'no ne opredelil ih s takoj cel'yu. Tak, opredelenie operator+=() dlya tipa complex nel'zya vyvesti iz opredelenij complex::operator+() i complex operator=(). Po istoricheskoj sluchajnosti okazalos', chto operacii = (prisvaivanie), &(vzyatie adresa) i , (operaciya zapyataya) obladayut predopredelennymi svojstvami dlya ob容ktov klassov. No mozhno zakryt' ot proizvol'nogo pol'zovatelya eti svojstva, esli opisat' eti operacii kak chastnye: class X { // ... private: void operator=(const X&); void operator&(); void operator,(const X&); // ... }; void f(X a, X b) { a= b; // oshibka: operaciya = chastnaya &a; // oshibka: operaciya & chastnaya a,b // oshibka: operaciya , chastnaya } S drugoj storony, mozhno naoborot pridat' s pomoshch'yu sootvetstvuyushchih opredelenij etim operaciyam inoe znachenie. 7.2.3 Operatornye funkcii i pol'zovatel'skie tipy Operatornaya funkciya dolzhna byt' libo chlenom, libo imet' po krajnej mere odin parametr, yavlyayushchijsya ob容ktom klassa (dlya funkcij, pereopredelyayushchih operacii new i delete, eto ne obyazatel'no). |to pravilo garantiruet, chto pol'zovatel' ne sumeet izmenit' interpretaciyu vyrazhenij, ne soderzhashchih ob容ktov pol'zovatel'skogo tipa. V chastnosti, nel'zya opredelit' operatornuyu funkciyu, rabotayushchuyu tol'ko s ukazatelyami. |tim garantiruetsya, chto v S++ vozmozhny rasshireniya, no ne mutacii (ne schitaya operacij =, &, i , dlya ob容ktov klassa). Operatornaya funkciya, imeyushchaya pervym parametr osn