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.