next up previous contents
Elõre: Virtuális függvények deklarálása Fel: Virtuális függvények Vissza: Virtuális függvények

Késõi összerendelés

Az elõzõ részekben egy összetett grafikus programrendszer lehetséges alaptípusainak példáján mutattuk be az OOP egyes jellemzõit. A point típus a location-ból lett származtatva, ugyanígy point-ból létrehozhatunk egy vonalakat leíró osztályt, amelyet például line-nak nevezhetünk, és mondjuk line-ból származtathatunk mindenféle poligont. A poligonokra két vázlatos példát mutattunk (triangle, rectangle), ezekbõl felépítve pedig már egy "valóságos" objektumok leírására való típus, a house vázlatos leírását adtuk. (Ezek a példák természetesen nem egy mûködõ program létrehozására, hanem sokkal inkább egy-egy OOP fogalom megvilágítására voltak kihegyezve.) Az eddigi példák többségének ugyanakkor volt egy közös vonása, nevezetesen az, hogy deklaráltunk bennük egy-egy show azonosítójú függvénymezõt azzal a céllal, hogy az az adott típusú objektumot a képernyõn megjelenítse. Az alapvetõ különbség az általunk elképzelt objektumtípusok között az, hogy milyen módon kell õket a képernyõre kirajzolni. Az egésznek van egy nagy hátránya: akárhányszor egy újabb alakzatot leíró osztályt definiálunk, a hozzátartozó show függvényt mindannyiszor újra kell írnunk. Ennek az az oka, hogy a C++ alapvetõen háromféleképpen tudja az azonos névvel ellátott függvényeket egymástól megkülönböztetni:

Az ilyenfajta függvényhivatkozások feloldása mind fordítási idõben történik. Ezt nevezzük korai, vagy statikus összrendelésnek (early binding, static binding).

Egy komolyabb grafikus rendszer esetében azonban igen gyakran elõfordul az a helyzet, hogy csak az osztálydeklarációk állnak rendelkezésre forrásállományban (.h kiterjesztésû include file-okban), maguk a függvénymezõ definíciók pedig csak tárgykód formájában vannak meg (.obj file-okban). Ebben az esetben, ha a felhasználó a meglévõ osztályokból akar újabbakat származtatni, akkor a korai kötés korlátai miatt nem lesz könnyû dolga az alakzatmegjelenítõ rutinok megírásánál. A C++ ezt a problémát az ún. késõi kötés (late binding) lehetõségével hidalja át. Ezt a késõi kötést speciális függvénymezõk, a virtuális függvények (virtual functions) teszik lehetõvé.

Az alapkoncepció az, hogy a virtuális függvényhívásokat csak futási idõben oldja fel a C++ rendszer - innen származik a késõi kötés elnevezés. Tehát azt a döntést, hogy például melyik show függvényt is kell aktivizálni, el lehet halasztani egészen addig a pillanatig, amikor a ténylegesen megjelenítendõ objektum pontos típusa ismertté nem válik a program futása során. Egy virtuális show függvény, amely el van "rejtve" egy elõzetesen lefordított modulkönyvtár egy B alaposztályában, nem lesz véglegesen hozzárendelve a B típusú objektumokhoz olyan módon, ahogy azt normál függvénymezõk esetében láttuk. Nyugodtan származtathatunk egy D osztályt a B-bõl, definiálhatjuk a D típusú objektumok megjelenítésére szolgáló show függvényt. Az új forrásprogramot (amelyik az új D osztály definícióját is tartalmazza) lefordítva kapunk egy .obj file-t, amelyet hozzászerkeszthetünk a grafikus könyvtárhoz (a .lib file-hoz). Ezután a show függvényhívások, függetlenül attól, hogy a B alaposztálybeli függvénymezõre, vagy az új, D osztálybeli függvénymezõre vonatkoznak, helyesen lesznek végrehajtva. Lássuk ezt az egész fogalomkört egy kevésbé absztrakt példán.

Tekintsük a jól bevált point típust, és egészítsük ki azt egy olyan függvénnyel, amelyik egy ilyen típusú objektum által reprezentált pontot a képernyõ egyik helyérõl áthelyezi egy másikra helyre. A point-ból azután származtassunk egy körök leírására alkalmas circle típust (olyan meggondolás alapján, hogy egy pont egy olyan kör, amelynek sugara 0, tehát a point típust egyetlen adatmezõvel, a sugárral kell kiegészíteni).

enum   colortype { black, red, blue, green, yellow, white };
class location {
                  protected:
                    int  x;
                    int  y;
                  public:
                    location(int ix, int iy); 
                    ~location(void);                                           
   
                    int get_x(void);
                    int get_y(void);
                };
class point : public location
                {
                  protected:
                    colortype color;
                  public:
                    colortype get_color(void);
                    void      show(void);
                    void      hide(void);
                    void      move(int dx, int dy);
                    point(int ix,int iy);
                    ~point(void);                                              
   
                };
 // A point:: fuggvenyek definicioja:
...
void point::move(int dx, int dy)
{   // dx, dy offsetekkel athelyezi
    if (color) hide( ); // Ez itt a point::hide
    x += dx;
    y += dy;
    if (color) show( ); // Ez itt a point::show
}
class circle : public point
                {
                  protected:
                    int       radius;       // A sugara
                  public: // Konstruktorhoz kezdeti x,y,r
                    circle(int ix,int iy,int ir);
                    colortype get_color(void);
                    void      show(void);
                    void      hide(void);
                    void      move(int dx, int dy);
                    void      zoom(int factor);
                };
// A circle:: fuggvenyek definicioja:
...
void circle::move(int dx, int dy)
{   // dx, dy offsetekkel athelyezi
    if (color) hide( ); // Ez itt a circle::hide
    x += dx;
    y += dy;
    if (color) show( ); // Ez itt a circle::show
}
...
Figyeljük meg, hogy a két move függvény mennyire egyforma! A visszatérési típusuk void és a paraméter-szignatúrájuk is azonos, sõt, még a függvénytörzsek is azonosnak tûnnek. Hát akkor mi alapján tudja a fordító, hogy mikor melyik függvényrõl van szó? Ez esetben - ahogy azt a normál függvénymezõk esetében láttuk - a move függvényeink abban különböznek, hogy más-más osztályhoz tartoznak ( point::move, illetve circle::move). Fölvetõdik a kérdés: Ha a két move függvény törzse is egyforma, akkor miért kell belõlük 2 példány, miért ne örökölhetné circle ezt a függvényt point-tól? Nos azért, mert csak felületes ránézésre egyforma a két függvénytörzs, ugyanis más-más hide, illetve show függvényeket hívnak - ezeknek csak a neve és a szignatúrája azonos. Ha circle   a   point-tól  örökölné  a move függvényt, akkor az nem a megfelelõ hide, illetve show függvényeket hívná: nevezetesen a körre vonatkozó függvények helyett a pontra vonatkozókat aktivizálná. Ez azért van így, mert a korai kötés következtében a point::hide, illetve a point::show lenne a point típus move függvényéhez hozzárendelve, és az öröklés által a circle típus move függvénye is ezeket függvényeket hívná. Ezt a problémát úgy oldhatjuk meg, ha a show, illetve a hide függvényeket virtuális fügvényekként deklaráljuk. Ekkor nyugodtan írhatunk egy közös move függvényt a point és a circle számára (pontosabban fogalmazva a circle típus örökölhetné a point típus move függvényét), és majd futási idõben fog kiderülni, hogy melyik show, illetve hide függvényt kell meghívni.


next up previous contents
Elõre: Virtuális függvények deklarálása Fel: Virtuális függvények Vissza: Virtuális függvények