Sanal Fonksiyonlara Yeniden Bakış
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 58 / 104.
Derleme zamanı ve çalışma zamanı polimorfizmi arasındaki farkı anladığımıza göre, sanal fonksiyonlara ve onları kullanmayı daha güvenli hale getiren override anahtar kelimesine daha derinlemesine bir göz atalım.
Bir fonksiyonu temel sınıfta virtual olarak işaretlediğinizde, türetilmiş sınıflar kendi uygulamalarını sağlayabilirler. override belirleyicisi, derleyiciye bir sanal fonksiyonu geçersiz kılmak istediğinizi açıkça belirtir:
class Animal {
public:
virtual void speak() {
std::cout << "Some sound" << std::endl;
}
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof!" << std::endl;
}
};override kullanmak kritiktir çünkü hataları derleme zamanında yakalar. Eğer yanlışlıkla fonksiyon adını yanlış yazarsanız veya yanlış parametreler kullanırsanız, derleyici sessizce yeni bir fonksiyon oluşturmak yerine sizi uyaracaktır:
class Cat : public Animal {
public:
void speek() override { // Derleyici hatası: geçersiz kılınacak fonksiyon yok
std::cout << "Meow!" << std::endl;
}
};final belirleyicisi daha fazla geçersiz kılmayı (overriding) engeller. Türetilmiş bir sınıfın belirli bir işlevi geçersiz kılan son sınıf olması gerektiğinde bunu kullanın:
class Bulldog : public Dog {
public:
void speak() override final {
std::cout << "Gruff woof!" << std::endl;
}
};
class TinyBulldog : public Bulldog {
void speak() override {} // Hata: final fonksiyon geçersiz kılınamaz
};Sanal fonksiyonları geçersiz kılarken her zaman override kullanın. Bu, niyetinizi belgeler ve derleyicinin mevcut bir sanal fonksiyonu gerçekten geçersiz kıldığınızı doğrulamasını sağlar.
Görev
KolaySanal fonksiyonlarla birlikte override ve final anahtar kelimelerinin gücünü gösteren bir bildirim sistemi kuralım. Bazı metotların daha fazla özelleştirilebildiği, bazılarının ise değişikliği önlemek için kilitlendiği bir bildirim işleyici hiyerarşisi oluşturacaksınız.
Kodunuzu üç dosya halinde düzenleyeceksiniz:
Notifier.h: Herhangi bir bildirim göndericiyi temsil eden temel birNotifiersınıfı tanımlayın:- Korumalı (protected) bir
std::string recipientüyesi - Alıcı adını alan ve saklayan bir yapıcı (constructor)
- Şunu yazdıran sanal bir
send(const std::string& message)metodu:Notifying <recipient>: <message> "Generic"dizesini döndüren sanal birgetType()metodu- Sanal bir yıkıcı (destructor)
- Korumalı (protected) bir
EmailNotifier.h:Notifiersınıfından miras alan birEmailNotifiersınıfı tanımlayın:- Özel (private) bir
std::string domainüyesi - Alıcı ve alan adını (domain) alan, alıcıyı temel sınıfa ileten bir yapıcı
send()metodunu şunu yazdıracak şekilde geçersiz kılın (override):Emailing <recipient>@<domain>: <message>getType()metodunu geçersiz kılın vefinalolarak işaretleyin — bu metot"Email"döndürmelidir
Ardından
EmailNotifiersınıfından miras alan birUrgentEmailNotifiersınıfı tanımlayın:- Alıcı ve alan adını alan, her ikisini de
EmailNotifiersınıfına ileten bir yapıcı send()metodunu şunu yazdıracak şekilde geçersiz kılın:[URGENT] Emailing <recipient>@<domain>: <message>- Not:
EmailNotifieriçindefinalolarak işaretlendiği için buradagetType()metodunu geçersiz kılamazsınız
- Özel (private) bir
main.cpp: Üç girdi okuyun (her biri ayrı bir satırda):- Alıcı adı
- E-posta alan adı (domain)
- Mesaj metni
Dinamik olarak üç bildirim nesnesi oluşturun: bir temel
Notifier, birEmailNotifierve birUrgentEmailNotifier— hepsi aynı alıcıyı (ve uygun olan yerlerde alan adını) kullanmalıdır. Bunları birNotifier*işaretçi dizisinde saklayın.Dizi boyunca döngü kurun ve her bildirim için
getType()kullanarak türünü yazdırın, ardından mesajınızla birliktesend()metodunu çağırın. Her girişi şu şekilde biçimlendirin:Type: <type> <send output>Her bildirim arasına boş bir satır yazdırın. İşiniz bittiğinde dinamik olarak ayrılmış nesnelerinizi temizleyin.
Örneğin, Alice, company.com ve Meeting at 3pm girdileriyle:
Type: Generic
Notifying Alice: Meeting at 3pm
Type: Email
Emailing Alice@company.com: Meeting at 3pm
Type: Email
[URGENT] Emailing Alice@company.com: Meeting at 3pmUrgentEmailNotifier sınıfının mesaj formatını özelleştirmek için send() metodunu nasıl geçersiz kılabildiğine, ancak getType() metodu final olarak işaretlendiği için "Email" türünü EmailNotifier sınıfından nasıl miras aldığına dikkat edin. Derleme zamanında herhangi bir imza uyuşmazlığını yakalamak için tüm geçersiz kılınan metotlarda override anahtar kelimesini kullanın.
Kopya kağıdı
override belirleyicisi, derleyiciye bir temel sınıftan sanal bir işlevi geçersiz kılma (override) niyetinde olduğunuzu açıkça bildirir:
class Animal {
public:
virtual void speak() {
std::cout << "Some sound" << std::endl;
}
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof!" << std::endl;
}
};override kullanmak, hataları derleme zamanında yakalar. İşlev adını yanlış yazarsanız veya yanlış parametreler kullanırsanız, derleyici sizi uyaracaktır:
class Cat : public Animal {
public:
void speek() override { // Derleyici hatası: geçersiz kılınacak işlev yok
std::cout << "Meow!" << std::endl;
}
};final belirleyicisi, türetilmiş sınıflarda daha fazla geçersiz kılma işlemini engeller:
class Bulldog : public Dog {
public:
void speak() override final {
std::cout << "Gruff woof!" << std::endl;
}
};
class TinyBulldog : public Bulldog {
void speak() override {} // Hata: final işlevi geçersiz kılınamaz
};Niyetinizi belgelemek ve derleyici doğrulamasını etkinleştirmek için sanal işlevleri geçersiz kılarken her zaman override kullanın.
Kendin dene
#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"
using namespace std;
int main() {
// Girdileri oku
string recipient;
string domain;
string message;
getline(cin, recipient);
getline(cin, domain);
getline(cin, message);
// TODO: 3 elemanlı bir Notifier* işaretçi dizisi oluşturun
// TODO: Dinamik olarak oluşturun:
// - Alıcı (recipient) ile bir temel Notifier
// - Alıcı (recipient) ve alan adı (domain) ile bir EmailNotifier
// - Alıcı (recipient) ve alan adı (domain) ile bir UrgentEmailNotifier
// TODO: Dizi boyunca döngü kurun ve her bildirimci (notifier) için:
// - "Type: " ve ardından getType() sonucunu yazdırın
// - Mesaj ile send() fonksiyonunu çağırın
// - Bildirimciler arasına boş bir satır yazdırın (sonuncusundan sonra değil)
// TODO: Dinamik olarak ayrılmış nesneleri temizleyin
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 Konteyner