Cast-ok
Czirkos Zoltán · 2019.02.27.
Néhány szó a cast-okról
Ez az írás jelentős átfedéseket tartalmaz a jegyzet 7. fejezetének 8. pontjával. Ha úgy érzed, maradtak olyan pontok, amiket nem sikerült tökéletesen megértened, érdemes lehet ennek ellenére elolvasnod.
A C-ben a cast-oláshoz (típuskonverzióhoz) az értéket előállító kifejezés elé kellett írni zárójelben, hogy milyen típusúvá szeretnénk konvertálni azt. Ez tulajdonképpen egy egyoperandusú prefix operátor:
int a = 2, b = 3;
double c = a / b; // 0
double d = (double) a / (double) b; // 0.6667
Ez egy univerzális konverzió, lényegében bármilyen típusú értékből bármilyet tud csinálni. Történjen a cast során bármi:
int *
→void *
, generikus programozáshoz.Point *
→char *
, memória mágiához.int
→double
az egész osztás elkerüléséhez.int const *
→int *
interfész konstansok miatt.
Az egyik probléma a zárójeles cast szintaxissal tehát az, hogy nem látszik a kódon a cast célja. A másik probléma, hogy ezek a cast-ok a programban észrevétlenek, nem lehet rájuk keresni. A kerek zárójelet ezer más dologra használjuk.
Ezért a C++-ban új operátorokat vezettek be a típuskonverzióhoz. Az új operátorok nevein látszódik a típuskonverzió célja, továbbá egyedi kulcsszavak lévén könnyű megtalálni őket a programban.
Az új cast-ok template függvényekhez hasonló szintaxissal látszanak a kódban:
valamilyen_cast<cél típus>(eredeti érték)
. A feladattól függően
const_cast
-ot, static_cast
-ot, dynamic_cast
-ot
vagy reinterpret_cast
-ot használunk; a módszertől függően pedig fordítási,
esetleg futási idejű ellenőrzést kapunk.
A static_cast
segítségével egymással „többé-kevésbé kompatibilis”
típusok között konvertálunk. Ez olyan konverziókra való, amelyek jogosak, de amelyeket
a fordító magától nem végezne el. Például:
- Nem karaktert, hanem karakterkódot szeretnénk kiírni (overload kiválasztása):
std::cout << static_cast<unsigned int>('A');
- Egész osztás elkerülése:
int a = 2, b = 3; std::cout << static_cast<double>(a) / static_cast<double>(b);
- Upcast osztályhierarchiában, ha tudjuk, hogy a cast helyes (bár erre
inkább a
dynamic_cast
való):Alakzat* a = alakzat_letrehoz(ALAKZAT_TEGLALAP); Teglalap* t = static_cast<Teglalap*>(a);
A dynamic_cast
segítségével osztályhierarchiákban ugrálhatunk. Ez
a cast, ahogy a neve is mutatja, futási idejű ellenőrzést is végez, és hibajelzést
ad, ha a cast helytelen. Ehhez a futási idejű ellenőrzéshez virtuális függvénytáblára van
szüksége, tehát ez csak polimorf osztályokon, azaz legalább egy virtuális függvénnyel
rendelkező osztályokon működik. (De ha más nem, egy virtuális destruktor úgyis mindig van.)
Ha pointert castolunk, helytelen típus esetén null értékű pointert ad:
Alakzat* a = alakzat_letrehoz(); // ad egy ismeretlen típusú alakzatot
Teglalap* t = dynamic_cast<Teglalap*>(a);
if (t == nullptr) {
std::cout << "Nem téglalap";
} else {
std::cout << "Téglalap, " <<
<< t->szelesseg() << "x" << t->magassag();
}
Referenciák esetén pedig std::bad_cast
típusú kivételt dob:
void valami(Alakzat &a) {
try {
Teglalap& t = dynamic_cast<Teglalap&>(a);
std::cout << "Téglalap, " <<
<< t.szelesseg() << "x" << t.magassag();
} catch(std::bad_cast) {
std::cout << "Nem téglalap";
}
}
Osztályhierarchiák esetén jobb elkerülni a cast-ok használatát. A túl sok (nullánál több? :D)
a dynamic_cast
egy code smell, ami helytelen
tervezésre utal; valószínűleg az ősosztályokból hiányoznak virtuális függvények.
Virtuális öröklés esetén mindenképp dynamic_cast
-ot kell használni. Ott ugyanis
az objektumrészletek az egész objektumhoz képest nem nulla ofszeten kezdődnek, és az ofszetek a virtuális
öröklés miatt eltérőek lehetnek a leszármazottakban. A pointerek közti eltolást a virtuális táblákból
lehet kiolvasni.
reinterpret_cast
-ot használunk minden egyéb esetben, ami nem fér a fenti
kategóriák valamelyikébe, vagy nem rakható össze azokból; olyan típusok között, amelyeknek semmi
közük egymáshoz. Ezek tipikusan a „pointermágiát” használó kódrészletek, pl. tetszőleges típusú
pointer unsigned char *
-gá való konvertálása a memória bájtonkénti elérése céljából.
Általában elmondható, hogy a reinterpret_cast
nem hordozható kódhoz vezet.
Egy int
bájtjainak kiírása:
int x = 0x11223344;
unsigned char* p = reinterpret_cast<unsigned char*>(&x);
for (size_t i = 0; i != sizeof(x); ++i)
printf("%02x ", p[i]);
Ez nem hordozható kód, a futási eredménye architektúrától függ (int
mérete,
ábrázolási módja, endianness stb.).
- Bjarne Stroustrup: The Design and Evolution of C++. Addison-Wesley, 1994.
- Code Smell – Wikipedia.