RicH and FamouS

       Home         Glosar IT                                                                                                                                                                                                              SUBSCRIBE NOW!
        

15.04.2009

Run-Time Type Checking (C++)

O intrebare frecvent intâlnită este "Cum pot afla/verifica tipul unui obiect la run-time?".

Să luam o problemă simplă:
Să se completeze codul de mai jos în aşa fel încât la primul apel funcţia CFoo::SpuneCevaMaiAnimalule să afişeze "Ham! " iar la al doilea să afişeze "Miau! ". Cu alte cuvinte, să se verifice la run-time dacă am transmis ca parametru un pointer la un obiect de tipul Caine sau la Pisica.

Cod:
class Animal {...};
class Caine : public Animal {...};
class Pisica : public Animal {...};
class CFoo
{
public:
void SpuneCevaMaiAnimalule(Animal*) {...}
};

int main(int argc, char* argv[])
{
Caine grivei;
Pisica mitzi;
CFoo foo;
foo.SpuneCavaMaiAnimalule(&grivei);
foo.SpuneCavaMaiAnimalule(&mitzi);
return 0;
}

O prima incercare:
Prima idee ar fi sa adaugăm un membru care să ţină o informaţie despre tip.

Cod:
#include <iostream>
using namespace std;
class Animal

{
public:
enum TipAnimal {TipCaine, TipPisica};
};

class Caine : public Animal
{
public:
Caine() : m_tipAnimal(TipCaine) {}
const TipAnimal m_tipAnimal;
};

class Pisica : public Animal
{
public:
Pisica() : m_tipAnimal(TipPisica) {}
const TipAnimal m_tipAnimal;
};

class CFoo
{
public:
void SpuneCevaMaiAnimalule(Animal*);
};

int main(int argc, char* argv[])
{
// ... (vezi codul din enuntul problemei)
}

void CFoo::SpuneCevaMaiAnimalule(Animal* pAnimal)
{
if(((Caine*)pAnimal)->m_tipAnimal == Animal::TipCaine)
cout << "Ham! ";
else if(((Pisica*)pAnimal)->m_tipAnimal == Animal::TipPisica)
cout << "Miau! ";
}

Priviti, vă rog, la implementarea lui CFoo::SpuneCevaMaiAnimalule şi gândiţi-vă că am avea nu două ci treizeci si nouă de tipuri (clase derivate din Animal).
Urâta rău! Greu de scris, greu de inţeles, uşor de făcut greseli. Deşi se putea si mai rău...

...se poate si mai bine

Hai sa înbunătăţim puţin soluţia!
Ţin numele clasei într-un membru static privat de tip string şi îl accesez printr-o funcţie virtuală.

Cod:
#include <iostream>
#include <string>
using namespace std;
class Animal

{
public:
virtual bool EsteDeTipul(const string&) = 0;
};

class Caine : public Animal
{
static const string m_nume_clasa;
public:
virtual bool EsteDeTipul(const string&);
};

class Pisica : public Animal
{
static const string m_nume_clasa;
public:
virtual bool EsteDeTipul(const string&);
};

class CFoo
{
public:
void SpuneCevaMaiAnimalule(Animal*);
};

int main(int argc, char* argv[])
{ }

const string Caine::m_nume_clasa = "Caine";
bool Caine::EsteDeTipul(const string& nume_clasa)
{
return (nume_clasa == m_nume_clasa);
}

const string Pisica::m_nume_clasa = "Pisica";
bool Pisica::EsteDeTipul(const string& nume_clasa)
{
return (nume_clasa == m_nume_clasa);
}

void CFoo::DoSomething(Animal* pAnimal)
{
if(pAnimal->EsteDeTipul("Caine"))
cout << "Ham! " << endl;
else if(pAnimal->EsteDeTipul("Pisica"))
cout << "Miau! " << endl;
}

Din nou uitându-ne la CFoo::SpuneCevaMaiAnimalule, pare acum ceva mai clară si mai uşor de scris.
Putem merge mai departe prin definirea urmatoarelor macro-uri:

Cod:
#define DECLARE_RUNTIME_CLASS
private:
static const string m_nume_clasa;
public:
virtual bool EsteDeTipul(const string&);
#define IMPLEMENT_RUNTIME_CLASS(class_name)
const string nume_clasa##::m_nume_clasa = #nume_clasa;
bool nume_clasa##::EsteDeTipul(const string& nume)
{return (nume == m_nume_clasa);}

Ne rămane şi mai puţin cod de scris:

Cod:
#include <iostream>
#include <string>
using namespace std;
class Animal

{
public:
virtual bool EsteDeTipul(const string&) = 0;
};

class Caine : public Animal
{
DECLARE_RUNTIME_CLASS
};

class Pisica : public Animal
{
DECLARE_RUNTIME_CLASS
};

class CFoo
{
public:
void SpuneCevaMaiAnimalule(Animal*);
};

int main(int argc, char* argv[])
{ }

IMPLEMENT_RUNTIME_CLASS(Caine)
IMPLEMENT_RUNTIME_CLASS(Pisica)

void CFoo::SpuneCevaMaiAnimalule(Animal* pAnimal)
{
if(pAnimal->EsteDeTipul("Caine"))
cout << "Ham! ";
else if(pAnimal->EsteDeTipul("Pisica"))
cout << "Miau! ";
}

In felul acesta ne-am apropiat de soluţia din MFC (Microsoft Foundation Classes library).

Daca folosim MFC, atunci e floare la ureche !
Majoritatea claselor din MFC deriva din CObject care printre altele implementează si un mecanism de verificare a tipului la run-time. Este un pic mai complicat decât în exemplul de mai sus dar tebuie să-l iertam pentru ca face mai multe decât ne dea pur şi simplu tipul obiectului. Mai multe despre "minunatele proprietati" ale clasei CObject pot face subiectul unui articol separat.
Oricum, dacă folosim MFC, problema noastră se poate rezolva simplu şi elegant, cam asa:

Cod:
#include <afx.h>
class Animal : public CObject
{
DECLARE_DYNAMIC(Animal)
};
class Caine : public Animal
{
DECLARE_DYNAMIC(Caine)
};

class Pisica : public Animal
{
DECLARE_DYNAMIC(Pisica)
};
class CFoo
{
public:
void SpuneCavaMaiAnimalule(Animal*);
};
int main(int argc, char* argv[])
{ }
IMPLEMENT_DYNAMIC(Animal, CObject)
IMPLEMENT_DYNAMIC(Caine, Animal)
IMPLEMENT_DYNAMIC(Pisica, Animal)

void CFoo::SpuneCavaMaiAnimalule(Animal* pAnimal)
{
if(pAnimal->IsKindOf(RUNTIME_CLASS(Caine)))
printf("Ham! ");
else if(pAnimal->IsKindOf(RUNTIME_CLASS(Pisica)))
printf("Miau! ");
}

O alta abordare: RTTI
O altă abordare este folosirea mecanismul RTTI (Run-Time Type Information) al limbajului C++. Acesta permite folosirea a doi operatori "speciali": typeid si dynamic_cast.
Prima rezolvare cu RTTI a problemei noastre este prin folosirea operatorului typeid. Acesta intoarce o referinţă la un type_info care ţine informaţii despre tipul obiectului pasat ca parametru.

Cod:
#include <iostream>
using namespace std;
class Animal {virtual void Dummy(){};};
class Caine : public Animal{};
class Pisica : public Animal{};
class CFoo

{
public:
void SpuneCavaMaiAnimalule(Animal*);
};

int main(int argc, char* argv[])
{ }

void CFoo::SpuneCavaMaiAnimalule(Animal* pAnimal)
{
const type_info& ti = typeid(*pAnimal);
if(ti == typeid(Caine))
cout << "Ham! ";
else if(ti == typeid(Pisica))
cout << "Miau! ";
}

Frumos!
Poate aţi observat că în clasa Animal am pus, deşi nu-mi trebuie la nimic, o funcţie virtuală. De ce? Simplu: ca sa meargă jucărica cu RTTI-ul, trebuie o clasă de tip "polimorfic", adică sa aibă cel puţin o funcţie virtuală.

Putem de asemenea să ne bazăm pe operatorul dynamic_cast. Acesta întoarce zero daca îi plasăm un pointer la un obiect de tipul care nu trebuie. Deci am putea scrie

Cod:
void CFoo::SpuneCavaMaiAnimalule(Animal* pAnimal)
{
if(dynamic_cast<Caine*>(pAnimal))
cout << "Ham! ";
else if(dynamic_cast<Pisica*>(pAnimal))
cout << "Miau! ";
}

La urma urmei la ce bun? Acestea fiind zise, am şi eu o intrebare: chiar e nevoie să ştim
tipul unui obiect la run-time? Eu zic ca nu. Putem uita toate chestiile de mai sus cu condiţia să ne amintim de polimorfism si să inţelegem pentru ce, de fapt, a băgat mosu' Stroustrup funcţiile virtuale în C++.

Cod:
#include <iostream>
using namespace std;
class Animal


{
public:
virtual void IacaSpun() = 0;
};

class Caine : public Animal
{
public:
virtual void IacaSpun();
};

class Pisica : public Animal
{
public:
virtual void IacaSpun();
};

class CFoo
{
public:
void SpuneCavaMaiAnimalule(Animal*)
};

int main(int argc, char* argv[])
{ }

void Caine::IacaSpun()
{
cout << "Ham! ";
}

void Pisica::IacaSpun()
{
cout << "Miau! ";
}

void CFoo::SpuneCavaMaiAnimalule(Animal* pAnimal)
{
pAnimal->IacaSpun();
}

O ultimă privire la CFoo::SpuneCavaMaiAnimalule. Frumoasă, curată, simplă, nu-i aşa?

Mi-am propus in articolul de faţă doar o introducere în "recunoaşterea tipurilor la run-time" prin rezolvarea unei probleme simple.
Succes !

    Blog din Moldova    FastCounter 

 
Copyright © 2008-2010 Foster1. All rights reserved.