Menu
Coddy logo textTech

Sanal Fonksiyonlar ve VTable

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 52 / 104.

virtual anahtar kelimesi, önceki derste gördüğümüz sorunu çözer. Bir metodu virtual olarak bildirdiğinizde, C++ hangi sürümün çağrılacağını işaretçi türüne göre değil, çalışma zamanındaki gerçek nesne türüne göre belirler.

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
Animal* ptr = &d;
ptr->speak();  // Çıktı: Woof!

Artık doğru metot çağrılıyor! override anahtar kelimesi isteğe bağlıdır ancak önerilir. Derleyiciye sanal bir fonksiyonu geçersiz kılmayı amaçladığınızı bildirir ve imzalar eşleşmediğinde hataları yakalar.

Bu nasıl çalışır? Bir sınıf sanal fonksiyonlara (virtual functions) sahip olduğunda, derleyici bir sanal tablo (vtable) oluşturur. Bu, sınıfın sanal fonksiyonlarına yönelik işaretçiler (pointers) içeren gizli bir arama tablosudur. Sanal fonksiyonlara sahip her nesne, kendi sınıfının vtable'ına işaret eden gizli bir işaretçi (vptr) saklar.

Bir işaretçi aracılığıyla sanal bir fonksiyon çağırdığınızda, program çalışma zamanında vtable içindeki doğru fonksiyon adresine bakar. Buna dinamik sevkiyat (dynamic dispatch) denir. Normal fonksiyon çağrılarına kıyasla küçük bir ek yük getirir, ancak güçlü polimorfik davranışlar sağlar.

Önemli bir kural: Eğer bir sınıf sanal fonksiyonlara sahipse ve bir temel sınıf olarak kullanılacaksa, yıkıcı fonksiyonu (destructor) da sanal olmalıdır. Bu, türetilmiş nesneler temel sınıf işaretçileri (base pointers) üzerinden silindiğinde düzgün bir temizleme işlemi yapılmasını sağlar:

class Animal {
public:
    virtual ~Animal() {}  // Sanal yıkıcı
    virtual void speak() {}
};
challenge icon

Görev

Kolay

Sanal fonksiyonların ve çalışma zamanı polimorfizminin (runtime polymorphism) gücünü gösteren bir medya oynatıcı sistemi oluşturalım. Her birinin farklı şekilde oynatıldığı bir medya türleri hiyerarşisi oluşturacak ve temel sınıf işaretçileri (base class pointers) üzerinden erişildiğinde bile sanal fonksiyonların nasıl doğru davranışı sağladığını göreceksiniz.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • Media.h: Oynatılabilir herhangi bir medyayı temsil eden temel bir Media sınıfı tanımlayın:
    • Korumalı (protected) bir std::string title üyesi
    • Bir başlık alan ve bunu saklayan bir yapıcı (constructor)
    • Şunu yazdıran sanal (virtual) bir play() metodu: Playing media: <title>
    • Şunu yazdıran bir sanal yıkıcı (virtual destructor): Media [<title>] destroyed
  • AudioTrack.h: Media sınıfından genel (public) olarak türetilen bir AudioTrack sınıfı tanımlayın:
    • Özel (private) bir std::string artist üyesi
    • Bir başlık ve sanatçı alan, başlığı temel sınıfa ileten ve sanatçıyı saklayan bir yapıcı
    • Şunu yazdırmak için override anahtar kelimesini kullanarak play() metodunu geçersiz kılın: Playing audio: <title> by <artist>
    • Şunu yazdıran bir yıkıcı: AudioTrack [<title>] destroyed
  • main.cpp: Üç girişi okuyun (her biri ayrı bir satırda):
    1. Ses parçası başlığı (string)
    2. Sanatçı adı (string)
    3. Video başlığı (string)

    Doğrudan main.cpp içinde Media sınıfından genel olarak türetilen bir VideoClip sınıfı tanımlayın:

    • Özel bir int duration üyesi (saniye cinsinden)
    • Bir başlık ve süre alan bir yapıcı (varsayılan süre 120)
    • Şunu yazdırmak için play() metodunu geçersiz kılın: Playing video: <title> (<duration>s)
    • Şunu yazdıran bir yıkıcı: VideoClip [<title>] destroyed

    Bir blok kapsamı (block scope) içinde üç Media* işaretçisinden oluşan bir dizi oluşturun. Dinamik olarak şunları atayın:

    • "Generic Media" başlıklı bir temel Media nesnesi
    • Giriş yapılan başlık ve sanatçıya sahip bir AudioTrack nesnesi
    • Giriş yapılan video başlığına sahip bir VideoClip nesnesi

    Dizi üzerinde döngü kurun ve her işaretçi için play() metodunu çağırın. Ardından tüm nesneleri ters sırada silin. Bloktan sonra şunu yazdırın: Playback complete!

Örneğin, Bohemian Rhapsody, Queen ve Nature Documentary girişleri ile:

Playing media: Generic Media
Playing audio: Bohemian Rhapsody by Queen
Playing video: Nature Documentary (120s)
VideoClip [Nature Documentary] destroyed
Media [Nature Documentary] destroyed
AudioTrack [Bohemian Rhapsody] destroyed
Media [Bohemian Rhapsody] destroyed
Media [Generic Media] destroyed
Playback complete!

Media* işaretçileri üzerinden play() çağrılmasının, sanal fonksiyonlar sayesinde nasıl doğru türetilmiş sınıf metodunu tetiklediğine dikkat edin. Ayrıca, sanal yıkıcının nasıl düzgün temizlik sağladığını gözlemleyin — bir temel sınıf işaretçisi üzerinden silme işlemi yapıldığında hem türetilmiş hem de temel sınıf yıkıcıları çalışır.

Kopya kağıdı

virtual anahtar kelimesi, hangi metodun çağrılacağını işaretçi türüne değil, gerçek nesne türüne göre belirleyerek çalışma zamanı polimorfizmini (çok biçimlilik) sağlar:

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
Animal* ptr = &d;
ptr->speak();  // Output: Woof!

override anahtar kelimesi isteğe bağlıdır ancak önerilir. Derleyiciye sanal bir fonksiyonu geçersiz kılma (override) niyetinde olduğunuzu bildirir ve imzalar eşleşmezse hataları yakalar.

Sanal fonksiyonlar nasıl çalışır: Derleyici, sınıfın sanal fonksiyonlarına yönelik işaretçiler içeren bir sanal tablo (vtable) oluşturur. Sanal fonksiyonlara sahip her nesne, sınıfının vtable'ına yönelik gizli bir işaretçi (vptr) saklar. Bir işaretçi aracılığıyla sanal bir fonksiyon çağrıldığında, program çalışma zamanında vtable'daki doğru fonksiyon adresine bakar. Buna dinamik sevkiyat (dynamic dispatch) denir.

Sanal yıkıcılar: Eğer bir sınıf sanal fonksiyonlara sahipse ve bir temel sınıf olarak kullanılacaksa, yıkıcısı da sanal olmalıdır. Bu, türetilmiş nesneler temel işaretçiler aracılığıyla silindiğinde düzgün bir temizlik yapılmasını sağlar:

class Animal {
public:
    virtual ~Animal() {}  // Sanal yıkıcı
    virtual void speak() {}
};

Kendin dene

#include <iostream>
#include <string>
#include "Media.h"
#include "AudioTrack.h"

using namespace std;

// TODO: Burada Media sınıfından genel olarak (publicly) türeyen VideoClip sınıfını tanımlayın
// - Özel (private) int duration üyesi (saniye cinsinden)
// - Yapıcı metot (constructor) başlık ve süre alır (varsayılan süre 120)
// - play() metodunu şu çıktıyı verecek şekilde geçersiz kılın (override): Playing video: <title> (<duration>s)
// - Yıkıcı metot (destructor) şunu yazdırır: VideoClip [<title>] destroyed

class VideoClip : public Media {
private:
    int duration;

public:
    // TODO: Yapıcı metodu (constructor) uygulayın
    VideoClip(const std::string& t, int d = 120) : Media(t) {
        // TODO: Süreyi (duration) saklayın
    }

    // TODO: play() metodunu geçersiz kılın (override)
    void play() override {
        // TODO: Bu metodu uygulayın
    }

    // TODO: Yıkıcı metodu (destructor) uygulayın
    ~VideoClip() {
        // TODO: Bu yıkıcı metodu (destructor) uygulayın
    }
};

int main() {
    // Girdileri oku
    string audioTitle;
    string artist;
    string videoTitle;
    
    getline(cin, audioTitle);
    getline(cin, artist);
    getline(cin, videoTitle);

    // TODO: Süslü parantezlerle bir blok kapsamı (block scope) oluşturun
    // Bloğun içinde:
    // 1. Üç adet Media* işaretçisinden (pointer) oluşan bir dizi oluşturun
    // 2. Dinamik olarak bellek ayırın (allocate):
    //    - "Generic Media" başlıklı bir temel Media nesnesi
    //    - Girdi başlığı ve sanatçısı ile bir AudioTrack nesnesi
    //    - Girdi video başlığı ile bir VideoClip nesnesi
    // 3. Dizi üzerinde döngü kurun ve her bir işaretçi için play() metodunu çağırın
    // 4. Tüm nesneleri ters sırada silin (delete)

    // TODO: Bloktan sonra şunu yazdırın: Playback complete!
    
    return 0;
}
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler