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 sözcüğü, önceki derste gördüğümüz sorunu çözer. Bir yöntemi sanal 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!Şimdi doğru yöntem çağrılıyor! override anahtar sözcüğü isteğe bağlıdır ancak önerilir. Derleyiciye sanal bir işlevi geçersiz kılmayı amaçladığını bildirir ve imzalar eşleşmezse hataları yakalar.
Bu nasıl çalışır? Bir sınıfta sanal işlevler olduğunda derleyici bir sanal tablo (vtable) oluşturur. Bu, sınıfın sanal işlevlerine yönelik işaretçileri içeren gizli bir arama tablosudur. Sanal işlevlere sahip her nesne, sınıfının vtable'ına yönelik gizli bir işaretçi (vptr) depolar.
Bir sanal işlevi bir işaretçi üzerinden çağırdığınızda program, çalışma zamanında doğru işlev adresini vtable içinde arar. Buna dinamik gönderim denir. Normal işlev çağrılarına kıyasla küçük bir ek yük getirir, ancak güçlü polimorfik davranış sağlar.
Önemli bir kural: Bir sınıf sanal işlevlere sahipse ve temel sınıf olarak kullanılacaksa, yıkıcısı da sanal olmalıdır. Bu, türetilmiş nesneler temel sınıf işaretçileri üzerinden silinirken uygun temizlemenin yapılmasını sağlar:
class Animal {
public:
virtual ~Animal() {} // Sanal yıkıcı
virtual void speak() {}
};Görev
KolaySanal işlevlerin ve çalışma zamanı çok biçimliliğinin 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 üzerinden erişildiğinde bile sanal işlevlerin doğru davranışı nasıl etkinleştirdiğini göreceksin.
Kodunu üç dosyaya ayıracaksın:
Media.h: Oynatılabilir herhangi bir medyayı temsil eden bir temelMediasınıfı tanımla:- Korumalı bir
std::string titleüyesi - Bir başlık alan ve bunu saklayan bir kurucu
- Şunu yazdıran bir sanal
play()yöntemi:Playing media: <title> - Şunu yazdıran bir sanal yıkıcı:
Media [<title>] destroyed
- Korumalı bir
AudioTrack.h:AudioTracksınıfından herkese açık olarak kalıtım alan birMediasınıfı tanımla:- Özel bir
std::string artistüyesi - Bir başlık ve sanatçı alan, başlığı temel sınıfa aktaran ve sanatçıyı saklayan bir kurucu
play()yönteminioverrideanahtar sözcüğünü kullanarak geçersiz kıl ve şunu yazdır:Playing audio: <title> by <artist>- Şunu yazdıran bir yıkıcı:
AudioTrack [<title>] destroyed
- Özel bir
main.cpp: Üç girdi oku (her biri ayrı bir satırda):- Ses parçasının başlığı (string)
- Sanatçının adı (string)
- Videonun başlığı (string)
VideoClipsınıfından herkese açık olarak kalıtım alan birMediasınıfını doğrudan main.cpp içinde tanımla:- Özel bir
int durationüyesi (saniye cinsinden) - Bir başlık ve süre alan bir kurucu (varsayılan süreyi 120 olarak ayarla)
play()yöntemini geçersiz kılarak şunu yazdır:Playing video: <title> (<duration>s)- Şunu yazdıran bir yıkıcı:
VideoClip [<title>] destroyed
Bir blok kapsamı içinde üç adet
Media*işaretçisinden oluşan bir dizi oluştur. Dinamik olarak şunları ayır:- Başlığı "Generic Media" olan bir temel
Medianesnesi - Girdi olarak alınan başlık ve sanatçıyla bir
AudioTracknesnesi - Girdi olarak alınan video başlığıyla bir
VideoClipnesnesi
Dizi üzerinde döngü kurarak her işaretçi üzerinde
play()çağır. Ardından tüm nesneleri ters sırayla sil. Bloktan sonra şunu yazdır:Playback complete!
Örneğin girdiler Bohemian Rhapsody, Queen ve Nature Documentary olduğunda:
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!play() işaretçileri üzerinden Media* çağrısının, sanal işlevler sayesinde doğru türetilmiş sınıf yöntemini çağırdığına dikkat et. Ayrıca sanal yıkıcının uygun temizlemeyi nasıl sağladığını gözlemle: bir temel sınıf işaretçisi üzerinden silme yapıldığında hem türetilmiş sınıfın hem de temel sınıfın yıkıcıları çalışır.
Kendin dene
#include <iostream>
#include <string>
#include "Media.h"
#include "AudioTrack.h"
using namespace std;
// TODO: VideoClip sınıfını burada Media'dan public olarak miras alacak şekilde tanımla
// - Private int duration üyesi (saniye cinsinden)
// - Constructor title ve duration alır (varsayılan duration 120)
// - Override play() to print: Playing video: <title> (<duration>s)
// - Destructor prints: VideoClip [<title>] destroyed
class VideoClip : public Media {
private:
int duration;
public:
// TODO: Constructor'ı implement et
VideoClip(const std::string& t, int d = 120) : Media(t) {
// TODO: duration değerini sakla
}
// TODO: play() metodunu override et
void play() override {
// TODO: Bu metodu implement et
}
// TODO: Destructor'ı implement et
~VideoClip() {
// TODO: Bu destructor'ı implement et
}
};
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ı oluştur
// Blok içinde:
// 1. Üç Media* işaretçisinden oluşan bir dizi oluştur
// 2. Dinamik olarak ayır:
// - A base Media object with title "Generic Media"
// - Girilen başlık ve sanatçı ile bir AudioTrack nesnesi
// - Girilen video başlığı ile bir VideoClip nesnesi
// 3. Diziyi döngüyle gez ve her işaretçi üzerinde play() çağır
// 4. Tüm nesneleri ters sırada sil
// TODO: After the block, print: Playback complete!
return 0;
}
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
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic KonteynerKendi başına pratik yap: Online C++ derleyicisi