Yükleniyor...

C++ Polimorfizm: Sanal Fonksiyonlar ve Vtable

C++’ta polimorfizmi öğrenin. Sanal fonksiyonlar, vtable yapısı ve dinamik bağlama ile esnek nesne yönelimli tasarımlar geliştirin.

Polimorfizm (çok biçimlilik), aynı arayüzü (taban tür) paylaşan nesnelerin çalışma zamanında farklı davranışlar sergilemesini sağlar. C++’ta dinamik polimorfizm sanal (virtual) fonksiyonlar aracılığıyla gerçekleştirilir ve derleyici bu mekanizmayı tipik olarak vtable (virtual table) ve her nesnede bulunan vptr (virtual pointer) ile uygular. Bu makalede sanal fonksiyonların nasıl çalıştığını, vtable/vptr düzenini, performans etkilerini ve en iyi uygulamaları gerçekçi örneklerle inceleyeceğiz.


1) Dinamik Polimorfizm Nedir?

Dinamik polimorfizm, hangi fonksiyonun çağrılacağının çalışma zamanında belirlenmesidir. Bu sayede taban sınıf referansı veya işaretçisi üzerinden türetilmiş sınıfın davranışı çağrılabilir.


#include <iostream>
#include <memory>
using namespace std;

struct Book {
    virtual ~Book() = default;           // polimorfik taban için şart
    virtual void PrintInfo() const {     // sanal fonksiyon
        cout << "[Book] Generic info" << endl;
    }
};

struct EBook : Book {
    void PrintInfo() const override {
        cout << "[EBook] Digital edition" << endl;
    }
};

struct PrintedBook : Book {
    void PrintInfo() const override {
        cout << "[PrintedBook] Physical edition" << endl;
    }
};

int main() {
    unique_ptr<Book> a = make_unique<EBook>();
    unique_ptr<Book> b = make_unique<PrintedBook>();

    a->PrintInfo();  // [EBook] ...
    b->PrintInfo();  // [PrintedBook] ...
}

a ve b taban tür (Book) olarak görünse de çağrılar çalışma zamanında gerçek türe (EBook/PrintedBook) yönlendirilir.


2) vtable ve vptr Nasıl Çalışır?

Bir sınıf en az bir virtual fonksiyona sahip olduğunda derleyici o sınıf için bir vtable üretir. Nesnelerin içinde genellikle vptr adlı gizli bir gösterici bulunur ve bu gösterici ilgili sınıfın vtable’ını işaret eder.

Çağrı sırasında: nesne.vptr → vtable → gerçek fonksiyon adresi.


Book (vptr) ──► vtable(Book)       : &Book::PrintInfo
EBook (vptr) ─► vtable(EBook)       : &EBook::PrintInfo
Printed (vptr)► vtable(PrintedBook) : &PrintedBook::PrintInfo

Bu dolaylı yönlendirme küçük bir çağrı maliyeti ekler; çoğu uygulamada önemsenmeyecek düzeydedir.


3) override, final ve virtual Destructor

override anahtar sözcüğü, tabandaki sanal bir fonksiyonun gerçekten ezildiğini derleme zamanında doğrular. final ise metot veya sınıfın daha fazla ezilemeyeceğini belirtir. Polimorfik tabanlarda virtual destructor şarttır; aksi halde taban işaretçi üzerinden silme tanımsız davranışa yol açabilir.


struct Book {
    virtual ~Book() = default;
    virtual double Price() const { return 0.0; }
};

struct EBook final : Book {
    double Price() const override { return 99.0; }
};

// struct SpecialEBook : EBook {}; // HATA: EBook final

4) Statik Polimorfizm ile Farkı (Overload/Template)

Overload (aynı isim, farklı imza) ve şablonlar (templates) derleme zamanında çözümlenir, vtable kullanılmaz. Dinamik polimorfizm ise çalışma zamanında sanal çağrı ile çözümlenir. İhtiyaca göre doğru yaklaşımı seçin:


5) Polimorfik Arayüz Tasarımı (Book Örneği)

Ortak davranışı taban sınıfta soyutlayıp alt sınıflarda özelleştirelim. Fiyat ve baskı/dijital detaylar örneği:


#include <iostream>
#include <memory>
#include <string>
using namespace std;

class Book {
protected:
    string title, author;
public:
    Book(string t, string a) : title(t), author(a) {}
    virtual ~Book() = default;

    virtual double FinalPrice() const = 0;   // saf sanal: soyut sözleşme
    virtual void PrintInfo() const {
        cout << title << " - " << author
             << " | " << FinalPrice() << " TL" << endl;
    }
};

class EBook : public Book {
    double base, vat; // dijital fiyat ve KDV
public:
    EBook(string t, string a, double b, double v)
        : Book(t,a), base(b), vat(v) {}

    double FinalPrice() const override {
        return base*(1.0+vat);
    }
};

class PrintedBook : public Book {
    double base, ship; int pages; bool hard;
public:
    PrintedBook(string t, string a, double b, double s, int p, bool h)
        : Book(t,a), base(b), ship(s), pages(p), hard(h) {}

    double FinalPrice() const override {
        double cost = base + ship + 0.04*pages;
        if (hard) cost *= 1.1;
        return cost;
    }
};

int main() {
    unique_ptr<Book> b1 = make_unique<EBook>("Modern C++", "A. Dev", 100, 0.10);
    unique_ptr<Book> b2 = make_unique<PrintedBook>("Clean Code", "R. Martin", 120, 20, 464, true);

    b1->PrintInfo(); // EBook davranışı
    b2->PrintInfo(); // PrintedBook davranışı
}

6) Nesne Dilimleme (Object Slicing) ve Referans/Pointer Kullanımı

Türetilmiş nesneyi değer olarak taban tipe kopyalarsanız, türe özgü kısım dilimlenir. Bu nedenle polimorfizm için her zaman taban referansı veya taban pointer kullanın.


PrintedBook pb("X","Y",100,20,200,false);
Book base = pb;   // SLICING: Book kısmı kopyalanır, PrintedBook kısmı kaybolur
base.PrintInfo(); // Book davranışı (türe özel yok)

7) dynamic_cast ve Tür Güvenliği

Taban türden türe güvenli dönüşüm için dynamic_cast kullanılır (RTTI gerekir). Başarısız olursa nullptr döner (pointer’larda).


void PrintIfPrinted(const Book* b) {
    if (auto pb = dynamic_cast<const PrintedBook*>(b)) {
        cout << "Printed fiyat: " << pb->FinalPrice() << endl;
    } else {
        cout << "PrintedBook degil." << endl;
    }
}

8) Performans Notları


9) final Sınıf/Metot, override Hataları ve En İyi Uygulamalar


10) vtable’ın Görselleştirilmiş Etkisi (Adım Adım)

  1. Sınıf en az bir sanal fonksiyon içerir → derleyici sınıf için vtable üretir.
  2. Nesne oluşturulduğunda vptr, sınıfın vtable’ını işaret eder.
  3. Çağrı yapılınca derleyici statik adres yerine vptr→vtable üzerinden gerçek adresi okur.
  4. Türe ezmesi (override) vtable’daki ilgili girdi adresini değiştirir.

11) Sık Yapılan Hatalar


12) Tam Örnek: Polimorfik Koleksiyon


#include <iostream>
#include <vector>
#include <memory>
using namespace std;

class Book {
public:
    virtual ~Book() = default;
    virtual double FinalPrice() const = 0;
    virtual void Print() const = 0;
};

class EBook : public Book {
    double base, vat;
public:
    EBook(double b, double v) : base(b), vat(v) {}
    double FinalPrice() const override { return base*(1+vat); }
    void Print() const override {
        cout << "[EBook] " << FinalPrice() << " TL" << endl;
    }
};

class PrintedBook : public Book {
    double base, ship; int pages;
public:
    PrintedBook(double b, double s, int p) : base(b), ship(s), pages(p) {}
    double FinalPrice() const override { return base + ship + 0.05*pages; }
    void Print() const override {
        cout << "[Printed] " << FinalPrice() << " TL" << endl;
    }
};

int main() {
    vector<unique_ptr<Book>> items;
    items.push_back(make_unique<EBook>(100, 0.1));
    items.push_back(make_unique<PrintedBook>(120, 20, 400));

    for (const auto& it : items) it->Print(); // dinamik polimorfizm
}

13) TL;DR

  • Dinamik polimorfizm C++’ta virtual fonksiyonlarla sağlanır.
  • Derleyici, sanal çağrıları vtable/vptr ile uygular.
  • Polimorfik tabanlarda virtual destructor zorunludur.
  • override imza doğruluğu sağlar; final daha fazla ezilmeyi engeller.
  • Polimorfizm için değerle kopyalamak yerine referans/pointer kullanın (slicing’e dikkat).
  • Performans maliyeti genellikle küçüktür; sıcak yollar için alternatifler (CRTP/overload) düşünülebilir.
  • Örnekler Visual Studio 2022 ve GCC 11+ ile doğrudan çalıştırılabilir.

İlişkili Makaleler