RTTI

RTTI je kratica za run-time type identification, odnosno identificiranje dinamičkog tipa objekta, kojeg dohvaćamo kroz referencu ili pokazivač, za vrijeme izvršavanja programa. Dva operatora omogućavaju dohvat dinamičkog tipa:

dynamic_cast i typeid operatore treba upotrebljavati samo na referencama i pokazivačima na polimorfne klase. Pri tome imamo definiciju.

dynamic_cast operator

Operator dynamic_cast koristimo za konverziju reference ili pokazivača na objekt baznog tipa u referencu ili pokazivač na tip koji je posredno ili neposredno izveden iz baznog tipa. On vrši provjeru je li promjena tipa dopustiva i stoga se izvodi za vrijeme izvršavanja programa.

simple-base-derived-diag.png
Base * p_base = new Derived();  // implicitna konverzija
Derived * p_derived = p_base;   // nije implicitna konverzija
                                // treba koristiti dynamic_cast

Mogući način uporabe dynamic_cast operatora pokazan je u sljedećem primjeru:

Primjer

#include <iostream>
#include <typeinfo>  // Da bismo mogli koristiti type_info klasu

using namespace std;

class Base {
    public:
        int x;
        virtual ~Base(){}  // polimorfna klasa
        // ...
};

class Derived : public Base {
    public:
        int y;
        // ...
};


int main()
{
    Base *pb = new Derived();
    Derived *pd = dynamic_cast<Derived *>(pb); // pb pokazuje na Derived objekt te je
                                               // konverzija dopustiva
    if(pd != 0)
        cout << " dynamic_cast<Derived *>(pb) je uspio " << endl;
    else
        cout << " dynamic_cast<Derived *>(pb) nije uspio " << endl;

    Base b_obj;
    Base & rb = b_obj;

    Derived & rbb = dynamic_cast<Derived &>(rb); // rb ne referira na Derived objekt
                                                 // te stoga dynamic_cast izbacuje izuzetak

    delete pb;

    return 0;
}

typeid operator

Operator typeid djeluje na izraz ili ime tipa i vraća objekt tipa std::type_info koji sadrži informacije o tipu izraza. Upotrebljavamo ga tipično kada želimo ispitati dinamički tip objekta koji dohvaćamo kroz pokazivač ili referencu na bazni tip.

#include <iostream>
#include <typeinfo>  // Da bismo mogli koristiti type_info klasu

using namespace std;

class Base {
    public:
        int x;
        virtual ~Base(){}  // polimorfna klasa
        // ...
};

class Derived : public Base {
    public:
        int y;
        // ...
};


int main()
{
    Base * p1 = new Base();
    Base * p2 = new Derived();

    // ....

    if( typeid(*p1) == typeid(*p2) )
        cout << "p1 i p2 pokazuju na objekt istog tipa!" << endl;
    else
        cout << "p1 i p2 pokazuju na objekte različitih tipova!" << endl;

    // ....

    return 0;
}

typeid operator (nastavak)

Operatoru typeid možemo dati i ime tipa kao u sljedećem slučaju:

if( typeid(*p2) == typeid(Derived) )
      cout << "p2 je tipa Derived!" << endl;
else
      cout << "p2 nije tipa Derived!" << endl;

Ovaj će kod ispisati p2 je tipa Derived!.

typeid(expr) vraća

Operator typeid može se primijeniti na izraz bilo kojeg tipa, što uključuje ugrađene tipove i konstante. Stoga moramo paziti da dereferenciramo pokazivač kada želimo odrediti tip objekta na koji on pokazuje. Naime, ako bismo u gornjem kodu pisali

if( typeid(p1) == typeid(p2) )  // uspoređujem tipove pokazivača a ne objekata

uspoređivali bismo tipove pokazivača koji su neovisni o dinamičkom tipu objekata na koje pokazuju.

Klasa type_info

Operator typeid vraća objekt tipa std::type_info koji je ovisan o implementaciji no minimalno std::type_info ima sljedeći oblik:

namespace std {
  class type_info {
  public:
     virtual ~type_info();
     bool operator==(const type_info& rhs) const;
     bool operator!=(const type_info& rhs) const;
     bool before(const type_info& rhs) const;
     const char* name() const;
  private:
     type_info(const type_info& rhs);
     type_info& operator=(const type_info& rhs);
  };
}

Pored operatora uspoređivanja interesantan dio klase je funkcija name() koja daje ime tipa. Samo ime koje se vraća ovisno je o implementaciji.

Ispisivanje imena tipova

cout  << typeid(double).name() << endl;
cout  << typeid(float).name() << endl;

double d = 3.0;
float  f = 3.0F;

cout << typeid(d).name() << endl;
cout << typeid(f).name() << endl;

Base * pb = new Derived();

cout << typeid(pb).name() << endl;
cout << typeid(*pb).name() << endl;

bi na jednoj platformi mogao ispisati:

d
f
d
f
P4Base
4Base

Primjena RTTI-a

Operator uspoređivanja (==) za stablo polimorfnih klasa.

Operator uspoređivanja možemo implementirati kao funkciju članicu klase ili kao globalnu (friend) funkciju. Budući da objekt bilo koje izvedene klase može biti dan kroz referencu na baznu klasu, operator == bi trebao uzeti dvije reference na baznu klasu. Uzmemo li stoga da imamo dvije klase: Base i Derived, kao u prethodnim primjerima, tada bi operator ==, kao funkcija članica, trebao imati signaturu

virtual bool ImeKlase::operator==(const Base& rhs) const;

Takav operator bi trebao biti prisutan u svakoj klasi i brinuti se o uspoređivanju objekata dane klase s drugim objektima.

Problem: da bismo mogli iskoristiti polimorfizam morali smo učiniti argument operatora == referencom na baznu klasu, ali takav operator ne može obaviti postavljenu zadaću jer kroz referencu na baznu klasu ne možemo dohvatiti varijable članice u izvedenoj klasi, dok operator uspoređivanja tipično treba usporediti sve varijable članice.

Rješenje: dinamičko određivanje tipova (RTTI).

Primjer

Uzmimo da imamo dvije polimorfne klase:

class Base {
    public:
        int x;
        virtual ~Base(){}  // polimorfna klasa
        Base(int x_ = 0) : x(x_) {}
        virtual bool operator==(const Base&) const;
        // ...
};

class Derived : public Base {
    public:
        int y;
        Derived(int x_=0, int y_=0) : Base(x_), y(y_) {}
        virtual bool operator==(const Base&) const;
        // ...
};

Ove klase definiraju virtualne operatore uspoređivanja koji svi uzimaju referencu na baznu klasu. Operator uspoređivanja bazne klase (Base) zna da je lijevi operand tipa Base, ali ne zna kojeg je tipa desni operand. Stoga pomoću typeid operatora ispituje je li desni operator isto tipa Base:

Nastavak

bool Base::operator==(const Base& rhs) const
{
    if(typeid(*this) != typeid(rhs)) return false;
    // o.k. imamo posla s objektima tipa Base
    if(x == rhs.x) return true;
    else           return false;
}

U izvedenoj klasi:

bool Derived::operator==(const Base& rhs) const
{
    if(typeid(*this) != typeid(rhs)) return false;

    // Tipovi su isti, nastavljamo s ispitivanjem
    // Ovaj cast je sada siguran
    const Derived & rd = dynamic_cast<const Derived &>(rhs);
    // Neka prvo bazna klasa obavi svoj posao
    if(Base::operator==(rhs))
    {
        if(y == rd.y) return true;
        else          return false;
    }
    else return false; // Bazni dijelovi nisu jednaki
}

Zadatak

Naša implementaciju funkcionira dobro, ali ima problem efikasnosti jer se typeid operator poziva i u izvedenoj klasi i u baznoj klasi. Kako biste to izbjegli napravite globalnu funkciju

bool operator==(const Base& lhs, const Base& rhs);

i učinite ju prijateljem klasa Base i Derived. Unutar tih klasa operatore jednakosti ćete zamijeniti s funkcijama koje obavljaju isti posao. Svaka klasa će imati funkciju članicu

virtual bool equal(const Base& rhs) const;

umjesto operatora uspoređivanja. Ideja optimizacije je sljedeća: Funkcija članica equal ne provjerava jesu li tipovi objekata čiju jednakost provjerava jednaki. To vrši, samo jednom, globalni operator uspoređivanja, a equal vrši samo uspoređivanje. U globalnom operatoru uspređivanja se, dakle, nakon provjere jednakosti tipova, poziva virtualna funkcija equal na jednom argumentu s drugim argumentom kao parametrom i ona obavlja uspoređivanje:

lhs.equal(rhs);

U ovakvom dizajnu nije dobro funkciju equal staviti u javnu sekciju klase jer je ona nesigurna za korištenje izvan ovog konteksta. Stoga je prirodnije staviti ju u protected sekciju klase.