Sanal Kalıtım
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 54 / 104.
Elmas problemi (diamond problem), bir sınıfın ortak bir temel sınıfı paylaşan iki sınıftan miras alması durumunda ortaya çıkar. Özel bir işlem yapılmadığında, türetilmiş sınıf ortak temel sınıfın iki kopyasına sahip olur; bu da belirsizliğe ve bellek israfına yol açar.
class Animal {
public:
int age;
};
class Mammal : public Animal {};
class Bird : public Animal {};
class Bat : public Mammal, public Bird {};
Bat b;
b.age = 5; // Hata: belirsiz - hangi 'age'?Bat sınıfı, biri Mammal ve diğeri Bird üzerinden olmak üzere iki ayrı Animal alt nesnesi içerir. Bu, probleme adını veren elmas biçimli kalıtım diyagramını oluşturur.
Sanal kalıtım (virtual inheritance), ortak taban sınıfın yalnızca bir kopyasının bulunmasını sağlayarak bunu çözer. Paylaşılan taban sınıftan kalıtım alırken virtual anahtar kelimesini ekleyin:
class Animal {
public:
int age;
Animal(int a = 0) : age(a) {}
};
class Mammal : virtual public Animal {
public:
Mammal(int a = 0) : Animal(a) {}
};
class Bird : virtual public Animal {
public:
Bird(int a = 0) : Animal(a) {}
};
class Bat : public Mammal, public Bird {
public:
Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};
Bat b(5);
b.age = 10; // Çalışır! Sadece bir tane 'age' mevcutturBat'in, Animal'ı kurucusunda doğrudan başlatması gerektiğine dikkat edin. Sanal kalıtım ile, ara sınıflardan bağımsız olarak sanal tabanı oluşturmaktan en çok türetilmiş sınıf sorumludur.
Görev
KolaySanal kalıtımın (virtual inheritance) elmas problemini (diamond problem) nasıl çözdüğünü gösteren bir çalışan yönetim sistemi kuralım. Hem Developer hem de Manager sınıflarından miras alan ve her ikisi de ortak bir Employee temel sınıfını paylaşan bir TeamLead hiyerarşisi oluşturacaksınız.
Kodunuzu dört dosya halinde düzenleyeceksiniz:
Employee.h: Ortak temel sınıf olanEmployeesınıfını şu özelliklerle tanımlayın:- Korumalı (protected) bir
std::string nameveint id - Her iki değeri de alan ve şunu yazdıran bir yapıcı (constructor):
Employee [<name>] hired with ID <id> - Şunu yazdıran genel (public) bir
getInfo()metodu:Employee: <name> (ID: <id>) - Şunu yazdıran sanal bir yıkıcı (virtual destructor):
Employee [<name>] record closed
- Korumalı (protected) bir
Developer.h:Employeesınıfından virtual public inheritance (sanal genel kalıtım) kullanan birDevelopersınıfı tanımlayın:- Korumalı bir
std::string languageüyesi - İsim, id ve dil alan bir yapıcı — isim ve id'yi
Employeesınıfına iletir, dili saklar ve şunu yazdırır:Developer [<name>] specializes in <language> - Şunu yazdıran genel bir
code()metodu:<name> is coding in <language> - Şunu yazdıran bir yıkıcı:
Developer [<name>] signed off
- Korumalı bir
Manager.h:Employeesınıfından virtual public inheritance kullanan birManagersınıfı tanımlayın:- Korumalı bir
int teamSizeüyesi - İsim, id ve ekip boyutu alan bir yapıcı — isim ve id'yi
Employeesınıfına iletir, ekip boyutunu saklar ve şunu yazdırır:Manager [<name>] leads a team of <teamSize> - Şunu yazdıran genel bir
manage()metodu:<name> is managing <teamSize> people - Şunu yazdıran bir yıkıcı:
Manager [<name>] stepped down
- Korumalı bir
main.cpp: Dört girdi okuyun (her biri ayrı bir satırda):- İsim (string)
- Çalışan ID'si (tamsayı)
- Programlama dili (string)
- Ekip boyutu (tamsayı)
Hem
Developerhem deManagersınıflarından genel olarak miras alan birTeamLeadsınıfı tanımlayın:- Dört parametrenin tamamını alan ve
Employee(sanal temel sınıf), ardındanDeveloperveManagersınıflarını doğrudan başlatması gereken bir yapıcı - Yapıcı şunu yazdırmalıdır:
TeamLead [<name>] ready to lead and code! - Sırasıyla
getInfo(),code()vemanage()metodlarını çağıran birshowRole()metodu - Şunu yazdıran bir yıkıcı:
TeamLead [<name>] promoted out
Bir blok kapsamı (block scope) içinde bir
TeamLeadnesnesi oluşturun,showRole()metodunu çağırın ve ardından nesnenin kapsam dışına çıkmasını sağlayın. Bloktan sonra şunu yazdırın:Organization restructured!
Örneğin, Alice, 101, C++ ve 5 girdileriyle:
Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!Yalnızca bir Employee yapıcı çağrısı ve bir Employee yıkıcı çağrısı olduğuna dikkat edin — sanal kalıtım, paylaşılan temel sınıfın yalnızca bir kopyasının var olmasını sağlar. TeamLead, Employee sınıfını doğrudan başlatmalıdır çünkü sanal kalıtımda, en çok türetilen sınıf (most derived class) sanal temel sınıfı oluşturmaktan sorumludur.
Kopya kağıdı
Elmas problemi (diamond problem), bir sınıfın ortak bir temel sınıfa sahip iki sınıftan miras alması durumunda ortaya çıkar; bu durum temel sınıfın iki kopyasının oluşmasına ve belirsizliğe yol açar.
class Animal {
public:
int age;
};
class Mammal : public Animal {};
class Bird : public Animal {};
class Bat : public Mammal, public Bird {};
Bat b;
b.age = 5; // Error: ambiguous - which 'age'?Sanal kalıtım (virtual inheritance), ortak temel sınıfın yalnızca bir kopyasının bulunmasını sağlayarak elmas problemini çözer. Miras alırken virtual anahtar kelimesini kullanın:
class Animal {
public:
int age;
Animal(int a = 0) : age(a) {}
};
class Mammal : virtual public Animal {
public:
Mammal(int a = 0) : Animal(a) {}
};
class Bird : virtual public Animal {
public:
Bird(int a = 0) : Animal(a) {}
};
class Bat : public Mammal, public Bird {
public:
Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};
Bat b(5);
b.age = 10; // Works! Only one 'age' existsSanal kalıtım kullanıldığında, ara sınıflardan bağımsız olarak, en alt türetilmiş sınıf (most derived class) sanal temel sınıfı kendi yapıcı metodunda (constructor) doğrudan başlatmalıdır.
Kendin dene
#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"
using namespace std;
// TODO: Hem Developer hem de Manager sınıflarından genel (public) olarak miras alan TeamLead sınıfını tanımlayın
// Unutmayın: Sanal miras (virtual inheritance) ile TeamLead, Employee (sanal temel sınıf) sınıfını doğrudan başlatmalıdır
class TeamLead : public Developer, public Manager {
public:
// TODO: name, id, language ve teamSize parametrelerini alan kurucuyu (constructor) uygulayın
// Şu sırayla başlatılmalıdır: Önce Employee (sanal temel sınıf), ardından Developer, sonra Manager
// Şunu yazdırmalıdır: TeamLead [<name>] ready to lead and code!
TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
: Employee(name, id),
Developer(name, id, language),
Manager(name, id, teamSize) {
// TODO: Kurucu mesajını yazdırın
}
// TODO: showRole() metodunu uygulayın
// Bu sırayla getInfo(), code() ve manage() metodlarını çağırmalıdır
void showRole() {
// TODO: Üç metodu çağırın
}
// TODO: Yıkıcıyı (destructor) uygulayın
// Şunu yazdırmalıdır: TeamLead [<name>] promoted out
~TeamLead() {
// TODO: Yıkıcı mesajını yazdırın
}
};
int main() {
// Girdileri oku
string name;
int id;
string language;
int teamSize;
getline(cin, name);
cin >> id;
cin.ignore();
getline(cin, language);
cin >> teamSize;
// TODO: Bir blok kapsamı (block scope) içinde bir TeamLead nesnesi oluşturun
// showRole() metodunu çağırın, ardından kapsam dışına çıkmasına izin verin
{
// TODO: TeamLead oluşturun ve showRole() metodunu çağırın
}
// Bloktan sonra son mesajı yazdırın
cout << "Organization restructured!" << endl;
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