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:
typeid operator koji na danoj referenci ili pokazivaču vraća dinamički tip
objekta;
dynamic_cast operator koji dozvoljava sigurnu konverziju pokazivača ili
reference na bazni tip u pokazivač, odnosno referencu, na izvedeni tip.
dynamic_cast i typeid operatore treba upotrebljavati samo na referencama i pokazivačima
na polimorfne klase. Pri tome imamo definiciju.
dynamic_cast operatorOperator 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.
Base * p_base = new Derived(); // implicitna konverzija
Derived * p_derived = p_base; // nije implicitna konverzija
// treba koristiti dynamic_cast
dynamic_cast vraća nul-pokazivač.
dynamic_cast izbacuje std::bad_cast izuzetak (koji je definiran u
zaglavlju <typeinfo>).
Mogući način uporabe dynamic_cast operatora pokazan je u sljedećem primjeru:
#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 operatorOperator 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
expr ako je njegov statički tip polimorfna klasa;
expr ako je njegov statički tip nije polimorfna klasa;
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.
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.
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
==) 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).
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:
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
}
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.