RTTI (dynamic_cast, typeid)
Polimorfizm harika bir araç: base pointer üzerinden çalışırsın, gerçek tipe bakmana gerek kalmaz. Ama bazen — bazen — çalışma zamanında nesnenin gerçek tipini bilmen gerekir. Bu nesne gerçekten bir Circle mı yoksa Rectangle mı?
İşte RTTI (Run-Time Type Information) tam olarak bunu sağlar.
RTTI Nedir?
RTTI, C++'ın çalışma zamanında nesne tiplerini sorgulamaya izin veren mekanizmasıdır. İki ana aracı var:
`dynamic_cast` — Güvenli tip dönüşümü (downcasting)
`typeid` — Nesnenin tip bilgisini sorgulama
Analoji: Bir hayvanat bahçesinde çalışıyorsun. Elinde bir "Hayvan" etiketi var ama bakıcının "Bu hayvanı besle" dediğinde, hayvanın gerçek türünü bilmen gerekiyor. Aslan mı, tavşan mı? Aslana havuç veremezsin. İşte RTTI, o etiketin arkasına bakıp gerçek türü öğrenmeni sağlar.
dynamic_cast Kullanımı
dynamic_cast, bir base class pointer/referansını derived class pointer/referansına güvenli şekilde dönüştürür. Bu işleme downcasting denir (hiyerarşide yukarıdan aşağıya).
Pointer ile dynamic_cast
#include <iostream>
class Animal {
public:
virtual void speak() { std::cout << "...\n"; }
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void speak() override { std::cout << "Woof!\n"; }
void fetch() { std::cout << "Fetching ball!\n"; }
};
class Cat : public Animal {
public:
void speak() override { std::cout << "Meow!\n"; }
void purr() { std::cout << "Purrrr...\n"; }
};
int main() {
Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal);
if (dog) {
dog->fetch(); // OK — gerçekten Dog
}
Cat* cat = dynamic_cast<Cat*>(animal);
if (cat) {
cat->purr(); // Bu blok çalışmaz — animal Dog, Cat değil
} else {
std::cout << "Not a cat!\n";
}
delete animal;
}Çıktı:
Fetching ball!
Not a cat!Pointer ile dynamic_cast başarısız olursa `nullptr` döner. Bu yüzden her zaman null kontrolü yapmalısın.
Referans ile dynamic_cast
void handleAnimal(Animal& animal) {
try {
Dog& dog = dynamic_cast<Dog&>(animal);
dog.fetch();
} catch (const std::bad_cast& e) {
std::cout << "Not a dog: " << e.what() << "\n";
}
}
int main() {
Cat cat;
handleAnimal(cat); // std::bad_cast exception fırlatır
}Referans ile dynamic_cast başarısız olursa `std::bad_cast` exception fırlatır. Çünkü null referans diye bir şey yoktur.
dynamic_cast'in Kuralları
Virtual fonksiyon gerekli: Base class'ta en az bir virtual fonksiyon olmalı. Yoksa
dynamic_castçalışmaz — derleyici hata verir.Polimorfik tipler: Sadece polimorfik tipler (virtual fonksiyonu olan) ile kullanılabilir.
Pointer → nullptr, Referans → exception: Başarısızlık durumundaki davranışlar farklı.
💡 Neden virtual fonksiyon gerekli? Çünkü
dynamic_castvtable mekanizmasını kullanarak gerçek tipi belirler. Virtual fonksiyon yoksa vtable yoktur, tip bilgisi de yoktur.
Upcast vs Downcast vs Crosscast
Animal
/ \
Dog CatUpcast (yukarı dönüşüm): Derived → Base. Her zaman güvenli, dynamic_cast gerekmez.
Dog* dog = new Dog();
Animal* animal = dog; // Implicit upcast — her zaman güvenliDowncast (aşağı dönüşüm): Base → Derived. Tehlikeli olabilir, dynamic_cast gerekir.
Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal); // Güvenli downcastCrosscast (çapraz dönüşüm): Bir derived → başka bir derived. dynamic_cast gerekir.
// Çoklu kalıtım senaryosunda
class Flyable { virtual ~Flyable() = default; };
class Swimmable { virtual ~Swimmable() = default; };
class Duck : public Flyable, public Swimmable {};
Flyable* f = new Duck();
Swimmable* s = dynamic_cast<Swimmable*>(f); // Crosscasttypeid ve type_info
typeid operatörü, bir nesnenin veya tipin tip bilgisini döner. <typeinfo> header'ı gerekir.
#include <iostream>
#include <typeinfo>
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {};
class Cat : public Animal {};
int main() {
Dog dog;
Cat cat;
Animal* ptr = &dog;
// Statik tip bilgisi
std::cout << typeid(int).name() << "\n"; // "i" veya "int"
std::cout << typeid(double).name() << "\n"; // "d" veya "double"
// Polimorfik tip bilgisi
std::cout << typeid(*ptr).name() << "\n"; // "3Dog" veya "Dog"
// Tip karşılaştırma
if (typeid(*ptr) == typeid(Dog)) {
std::cout << "It's a Dog!\n";
}
// İki nesneyi karşılaştırma
if (typeid(dog) == typeid(cat)) {
std::cout << "Same type\n";
} else {
std::cout << "Different types\n";
}
}typeid'nin Davranışları
Polimorfik tipler: Eğer nesne polimorfik ise (virtual fonksiyonu varsa), typeid gerçek (runtime) tipi döner.
Non-polimorfik tipler: Virtual fonksiyon yoksa, typeid derleme zamanı tipini döner.
class NonPoly { // virtual fonksiyon yok
public:
void foo() {}
};
class DerivedNP : public NonPoly {};
int main() {
DerivedNP obj;
NonPoly* ptr = &obj;
// NON-polimorfik: derleme zamanı tipi
std::cout << typeid(*ptr).name() << "\n"; // "NonPoly" — DerivedNP DEĞİL!
}typeid ve nullptr
Polimorfik tipe sahip bir null pointer'ı dereference etmeye çalışırsan, std::bad_typeid exception fırlatılır:
Animal* ptr = nullptr;
// typeid(*ptr); // std::bad_typeid fırlatır!type_info Üyeleri
typeid bir std::type_info referansı döner:
const std::type_info& info = typeid(Dog);
info.name(); // İmplementasyona bağlı isim (mangled olabilir)
info == typeid(Cat); // Eşitlik karşılaştırma
info != typeid(Cat); // Eşitsizlik karşılaştırma
info.before(typeid(Cat)); // Sıralama (koleksiyon key'i için)⚠️ Dikkat:
type_info::name()fonksiyonunun döndürdüğü string derleyiciye bağlıdır. GCC'de mangled isim (3Dog) dönerken, MSVC'de okunabilir isim (class Dog) dönebilir. Bu string'e güvenme, karşılaştırma için==operatörünü kullan.
dynamic_cast vs static_cast
İkisi de tip dönüşümü yapar ama farklı şekilde:
Animal* animal = new Cat();
// static_cast — derleme zamanı, kontrol YOK
Dog* dog1 = static_cast<Dog*>(animal); // Derlenir, ama YANLIŞ!
// dog1->fetch(); // Undefined behavior — animal Cat, Dog değil!
// dynamic_cast — çalışma zamanı, kontrol VAR
Dog* dog2 = dynamic_cast<Dog*>(animal); // nullptr döner
if (dog2) {
dog2->fetch(); // Bu blok çalışmaz — güvenli
}
delete animal;| Özellik | static_cast | dynamic_cast |
|---|---|---|
| Kontrol zamanı | Derleme zamanı | Çalışma zamanı |
| Güvenlik | ❌ Kontrol yok | ✅ Kontrol var |
| Başarısızlık | Undefined behavior | nullptr veya exception |
| Performans | Sıfır maliyet | vtable lookup maliyeti |
| Virtual gerekli mi? | Hayır | Evet |
💡 Kural: Downcast yapıyorsan ve %100 emin değilsen
dynamic_castkullan.static_cast'i sadece tipin doğru olduğundan kesinlikle emin olduğunda kullan.
RTTI'nin Performans Maliyeti
RTTI bedava değil:
`dynamic_cast` maliyeti: Her çağrıda vtable üzerinden tip hiyerarşisini dolaşır. Derin hiyerarşilerde bu maliyet artabilir. Genellikle bir fonksiyon çağrısının birkaç katı.
`typeid` maliyeti: Genellikle sadece bir vtable erişimi —
dynamic_cast'ten çok daha hızlı.Bellek maliyeti: Her polimorfik sınıf için tip bilgisi (type_info nesnesi) saklanır. Bu genellikle ihmal edilebilir.
Binary boyutu: RTTI bilgisi çalıştırılabilir dosyanın boyutunu artırır.
Bazı projeler (oyun motorları, gömülü sistemler) RTTI'yi tamamen devre dışı bırakır:
g++ -fno-rtti main.cpp // GCC/ClangBu durumda dynamic_cast ve typeid kullanılamaz.
Ne Zaman RTTI Gerekli?
RTTI genellikle bir tasarım kokusudur (code smell). Çoğu zaman polimorfizm doğru kullanılırsa RTTI'ye gerek kalmaz.
RTTI Gerektiren Durumlar
1. Plugin sistemleri: Harici kütüphanelerden gelen nesnelerin tipini kontrol etmen gerektiğinde.
2. Serialization: Nesneyi dosyaya yazıp okurken gerçek tipi bilmen gerektiğinde.
3. Debugging/Logging: Hata ayıklama sırasında nesne tipini yazdırmak istediğinde.
RTTI Yerine Ne Kullanılmalı?
Çoğu durumda dynamic_cast yerine polimorfizm veya Visitor Pattern kullan:
// KÖTÜ — RTTI ile tip kontrolü
void processShape(Shape* shape) {
if (auto* circle = dynamic_cast<Circle*>(shape)) {
// circle-specific logic
} else if (auto* rect = dynamic_cast<Rectangle*>(shape)) {
// rectangle-specific logic
}
// Yeni şekil eklenince burayı da değiştirmelisin!
}
// İYİ — polimorfizm
class Shape {
public:
virtual void process() = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
void process() override {
// circle-specific logic
}
};
// Yeni şekil eklersen processShape değişmez⚠️ Kural: Kodunda
dynamic_castzincirleri (if-else if-else if) görüyorsan, bu genellikle polimorfizmin eksik veya yanlış kullanıldığının işaretidir. Önce tasarımı düzelt, RTTI son çare olsun.
Pratik Örnek: Güvenli Downcast
RTTI'nin meşru kullanım senaryosu — event handling sistemi:
#include <iostream>
#include <string>
#include <vector>
#include <memory>
class Event {
public:
virtual ~Event() = default;
virtual std::string type() const = 0;
};
class MouseEvent : public Event {
public:
int x, y;
MouseEvent(int x, int y) : x(x), y(y) {}
std::string type() const override { return "MouseEvent"; }
};
class KeyEvent : public Event {
public:
char key;
KeyEvent(char k) : key(k) {}
std::string type() const override { return "KeyEvent"; }
};
class ResizeEvent : public Event {
public:
int width, height;
ResizeEvent(int w, int h) : width(w), height(h) {}
std::string type() const override { return "ResizeEvent"; }
};
void handleEvent(Event* event) {
if (auto* mouse = dynamic_cast<MouseEvent*>(event)) {
std::cout << "Mouse at (" << mouse->x << ", " << mouse->y << ")\n";
} else if (auto* key = dynamic_cast<KeyEvent*>(event)) {
std::cout << "Key pressed: " << key->key << "\n";
} else if (auto* resize = dynamic_cast<ResizeEvent*>(event)) {
std::cout << "Resize to " << resize->width
<< "x" << resize->height << "\n";
}
}
int main() {
std::vector<std::unique_ptr<Event>> events;
events.push_back(std::make_unique<MouseEvent>(100, 200));
events.push_back(std::make_unique<KeyEvent>('A'));
events.push_back(std::make_unique<ResizeEvent>(1920, 1080));
for (auto& e : events) {
handleEvent(e.get());
}
}Çıktı:
Mouse at (100, 200)
Key pressed: A
Resize to 1920x1080Bu tarz event sistemlerinde her event tipinin kendine has verileri vardır. dynamic_cast ile güvenli şekilde doğru tipe dönüştürüp o verilere erişebilirsin. Daha büyük projelerde bu, genellikle Visitor Pattern veya event handler map'leri ile değiştirilir.
dynamic_cast Hiyerarşi Taraması
dynamic_cast sadece doğrudan alt sınıfa değil, hiyerarşide herhangi bir seviyeye dönüşüm yapabilir:
class A {
public:
virtual ~A() = default;
};
class B : public A {};
class C : public B {};
class D : public C {};
int main() {
D obj;
A* ptr = &obj;
// Hiyerarşide herhangi bir seviyeye downcast
B* b = dynamic_cast<B*>(ptr); // OK — D, B'nin alt sınıfı
C* c = dynamic_cast<C*>(ptr); // OK — D, C'nin alt sınıfı
D* d = dynamic_cast<D*>(ptr); // OK — doğrudan eşleşme
std::cout << std::boolalpha;
std::cout << "B*: " << (b != nullptr) << "\n"; // true
std::cout << "C*: " << (c != nullptr) << "\n"; // true
std::cout << "D*: " << (d != nullptr) << "\n"; // true
}dynamic_cast, vtable üzerinden hiyerarşiyi dolaşarak doğru tipi arar. Derin hiyerarşilerde bu tarama biraz daha uzun sürer ama pratikte ihmal edilebilir düzeydedir.
Enum veya Tag Tabanlı Alternatif
RTTI yerine bazen tip etiketi (type tag) kullanılır. Bu daha hızlıdır ama daha az güvenlidir:
class Shape {
public:
enum class Type { Circle, Rectangle, Triangle };
virtual Type getType() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
Type getType() const override { return Type::Circle; }
double radius;
};
void process(Shape* s) {
if (s->getType() == Shape::Type::Circle) {
auto* c = static_cast<Circle*>(s); // Güvenli — tipi kontrol ettik
std::cout << "Radius: " << c->radius << "\n";
}
}Bu yaklaşım RTTI'den hızlıdır (bir enum karşılaştırma vs vtable tarama) ama her yeni tip eklendiğinde enum'ı ve tüm switch/if bloklarını güncellemelisin. Open/Closed Principle'ı ihlal eder.
💡 Tercih sırası: 1) Polimorfizm (virtual fonksiyonlar), 2) Visitor Pattern, 3) Enum/tag tabanlı, 4) RTTI (dynamic_cast). İlkiyle çözebildiğin sürece diğerlerine geçme.
Visitor Pattern: RTTI'nin Zarif Alternatifi
dynamic_cast zincirlerinden kurtulmanın en bilinen yolu Visitor Pattern'dır. Her element kendi tipini biliyor, visitor'a kendini tanıtıyor:
#include <iostream>
#include <vector>
#include <memory>
// Forward declaration
class Circle;
class Rectangle;
// Visitor interface
class ShapeVisitor {
public:
virtual void visit(const Circle& c) = 0;
virtual void visit(const Rectangle& r) = 0;
virtual ~ShapeVisitor() = default;
};
// Base class
class Shape {
public:
virtual void accept(ShapeVisitor& visitor) const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
double radius;
Circle(double r) : radius(r) {}
void accept(ShapeVisitor& visitor) const override {
visitor.visit(*this); // "Ben Circle'ım" diye bildiriyor
}
};
class Rectangle : public Shape {
public:
double width, height;
Rectangle(double w, double h) : width(w), height(h) {}
void accept(ShapeVisitor& visitor) const override {
visitor.visit(*this); // "Ben Rectangle'ım" diye bildiriyor
}
};
// Concrete visitor — alan hesaplama
class AreaCalculator : public ShapeVisitor {
public:
void visit(const Circle& c) override {
std::cout << "Circle area: " << 3.14159 * c.radius * c.radius << "\n";
}
void visit(const Rectangle& r) override {
std::cout << "Rectangle area: " << r.width * r.height << "\n";
}
};
// Concrete visitor — çizim
class Renderer : public ShapeVisitor {
public:
void visit(const Circle& c) override {
std::cout << "Rendering circle with r=" << c.radius << "\n";
}
void visit(const Rectangle& r) override {
std::cout << "Rendering rectangle " << r.width << "x" << r.height << "\n";
}
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));
AreaCalculator calc;
Renderer renderer;
for (const auto& shape : shapes) {
shape->accept(calc);
shape->accept(renderer);
}
}Visitor Pattern ile:
dynamic_cast yok — tip kontrolü derleme zamanında çözülür
Yeni işlem eklemek kolay — yeni visitor yaz (AreaCalculator, Renderer, Exporter...)
Ama yeni tip eklemek zor — her visitor'a yeni
visitoverload'u eklemelisin
Bu trade-off'u anlamak önemli:
Yeni davranış eklenmesi sıksa → Visitor Pattern
Yeni tip eklenmesi sıksa → Virtual fonksiyonlar (klasik polimorfizm)
std::variant ve std::visit (C++17 Alternatif)
C++17 ile birlikte, küçük tip hiyerarşileri için kalıtım yerine std::variant kullanılabilir:
#include <iostream>
#include <variant>
#include <vector>
struct Circle { double radius; };
struct Rectangle { double width, height; };
struct Triangle { double base, height; };
using Shape = std::variant<Circle, Rectangle, Triangle>;
// Visitor — overloaded lambda'lar
struct AreaVisitor {
double operator()(const Circle& c) const {
return 3.14159 * c.radius * c.radius;
}
double operator()(const Rectangle& r) const {
return r.width * r.height;
}
double operator()(const Triangle& t) const {
return 0.5 * t.base * t.height;
}
};
int main() {
std::vector<Shape> shapes;
shapes.push_back(Circle{5.0});
shapes.push_back(Rectangle{3.0, 4.0});
shapes.push_back(Triangle{6.0, 3.0});
for (const auto& shape : shapes) {
double area = std::visit(AreaVisitor{}, shape);
std::cout << "Area: " << area << "\n";
}
}std::variant yaklaşımı:
Kalıtım yok, virtual yok, vtable yok → daha hızlı
Tüm tipler derleme zamanında biliniyor → tip güvenliği
Stack'te tahsis → heap allocation yok
Ama kapalı tip kümesi — runtime'da yeni tip eklenemez
Küçük, sabit tip kümeleri için std::variant çok iyi bir alternatiftir. Büyük, genişleyen hiyerarşiler için kalıtım hâlâ tercih edilir.
dynamic_cast ile Akıllı Fabrika
RTTI'nin pratikte meşru bir kullanımı — fabrika fonksiyonlarında tip kontrolü:
#include <iostream>
#include <memory>
#include <string>
class Widget {
public:
virtual std::string type() const = 0;
virtual ~Widget() = default;
};
class Button : public Widget {
std::string label;
public:
Button(const std::string& l) : label(l) {}
std::string type() const override { return "Button"; }
std::string getLabel() const { return label; }
};
class TextBox : public Widget {
std::string placeholder;
public:
TextBox(const std::string& p) : placeholder(p) {}
std::string type() const override { return "TextBox"; }
std::string getPlaceholder() const { return placeholder; }
};
// Widget'ı konfigüre eden fonksiyon — tip bilgisi gerekiyor
void configure(Widget* widget) {
if (auto* btn = dynamic_cast<Button*>(widget)) {
std::cout << "Configuring button: " << btn->getLabel() << "\n";
} else if (auto* txt = dynamic_cast<TextBox*>(widget)) {
std::cout << "Configuring textbox: " << txt->getPlaceholder() << "\n";
}
}
int main() {
auto btn = std::make_unique<Button>("OK");
auto txt = std::make_unique<TextBox>("Enter name...");
configure(btn.get());
configure(txt.get());
}Bu durumda her widget tipinin farklı konfigürasyon bilgisi var ve bu bilgiler base class arayüzünden erişilemez. dynamic_cast burada meşru bir kullanım.
Özet
RTTI (Run-Time Type Information), çalışma zamanında nesnenin gerçek tipini sorgulamayı sağlayan mekanizmadır.
`dynamic_cast` güvenli downcast yapar: pointer'da başarısız olursa
nullptr, referanstastd::bad_castdöner. En az bir virtual fonksiyon gerektirir.`typeid` nesnenin tip bilgisini
type_infoolarak döner; polimorfik tiplerde runtime tipi, diğerlerinde derleme zamanı tipi verir.RTTI performans maliyeti taşır ve bazı projeler (
-fno-rtti) ile devre dışı bırakılır.Çoğu
dynamic_castkullanımı bir tasarım eksikliğinin işaretidir — Visitor Pattern, std::variant veya düzgün polimorfizm ile çözmeyi dene.RTTI'nin meşru kullanım alanları vardır (plugin sistemleri, serialization, widget konfigürasyonu) ama her zaman son çare olarak düşün.
AI Asistan
Sorularını yanıtlamaya hazır