Ders 08 / 14
Kalıtım
Bir türetilmiş nesnenin içinde taşıdığı taban alt nesnesinin, aynı adreste durduğunun ve tek bir kurma/yıkma sırasının parçası olduğunun ölçümü.
Yayın Güncelleme
Bu derste
- Bir türetilmiş sınıfın taban alt nesnesini nesnenin bir parçası olarak tanımlar
- Taban ve türetilmiş nesnenin aynı adresi paylaştığını adres karşılaştırmasıyla ölçer
- Kurucu ve yıkıcı çağrı sırasının tek bir nesnenin iki aşaması olduğunu gözlemler
İçindekiler
Önceki konu bir nesnenin kendi kaynağını nasıl kurduğunu, kopyaladığını ve serbest bıraktığını ölçtü; sınıf tek başınaydı, başka bir sınıfa bağlı değildi. Bu konu bir sınıfın başka bir sınıfın üzerine nasıl kurulduğunu ele alıyor: kalıtım, bir sınıfı bir başkasının içine yerleştirir. Dersin ölçü sorusu aynı — bu ifade hangi nesneyle çalışıyor — ama artık nesnenin kendisi iki katmanlı: bir taban alt nesne ve onu saran bir türetilmiş nesne. Bu ders şunu soruyor: taban alt nesne, türetilmiş nesneden ayrı bir nesne mi, yoksa onun bir parçası mı?
Taban Alt Nesnesi ve Ortak Adres
Yonetici, Calisan’dan türüyor. Yonetici kurulduğunda önce Calisan yapıcısı
çalışıyor, sonra Yonetici’nin kendi gövdesi. Taban tipinden bir işaretçiyle (taban)
y’yi adlandırmak bir kopya çıkarmıyor mu, yoksa aynı adresi mi taşıyor — bunu adres
karşılaştırmasıyla ölçmek gerekiyor.
// kalitim_taban.cpp — turetilmis nesnenin taban alt nesnesi ve ortak adres
#include <iostream>
#include <string>
class Calisan {
public:
explicit Calisan(std::string ad) : ad_(ad) {
std::cout << "Calisan yapici: " << ad_ << "\n";
}
~Calisan() {
std::cout << "Calisan yikici: " << ad_ << "\n";
}
protected:
std::string ad_;
};
class Yonetici : public Calisan {
public:
Yonetici(std::string ad, int takim_no) : Calisan(ad), takim_no_(takim_no) {
std::cout << "Yonetici yapici: takim " << takim_no_ << "\n";
}
~Yonetici() {
std::cout << "Yonetici yikici\n";
}
private:
int takim_no_;
};
int main() {
Yonetici y("Aylin", 4);
Calisan* taban = &y;
std::cout << std::boolalpha;
std::cout << "y ile taban adresinin ayni olup olmadigi "
<< (static_cast<void*>(&y) == static_cast<void*>(taban)) << "\n";
return 0;
}
Calisan yapici: Aylin Yonetici yapici: takim 4 y ile taban adresinin ayni olup olmadigi true Yonetici yikici Calisan yikici: Aylin
CB1. Yonetici yapıcısı önce Calisan yapıcısını çağırıyor (“Calisan yapici:
Aylin”), sonra kendi gövdesini çalıştırıyor (“Yonetici yapici: takim 4”) — bu, iki ayrı
nesnenin art arda kurulması değil, tek bir nesnenin iki aşamalı kurulması. taban = &y ataması bir kopya çıkarmıyor, yalnız aynı adresi taban tipinden bir işaretçiyle
adlandırıyor: static_cast<void*>(&y) == static_cast<void*>(taban) karşılaştırması
bunu doğruluyor, true. Çıkışta y kapsam dışına çıkarken yıkım sırası kurmanın tam
tersini izliyor — önce Yonetici‘nin kendi yıkıcısı, sonra Calisan‘ınki — yine tek
nesnenin iki aşamalı çözülüşü. (M08/K02’de sorulan soru çağrıyı hangi tipin çözdüğüydü;
burada soru değişti — çağrı değil, ifadenin işaret ettiği nesne ölçülüyor, ve
Java’da nesne dilimleme yokken C++’ta taban tipe değerle atama yeni ve eksik bir
nesne kurar; burada yalnız işaretçi erişimi var, dilimleme yok.) Sınıf parça:
taban’ın gösterdiği şey y’den bağımsız bir nesne değil, y’nin içindeki taban alt
nesnesi.
Taban Referansıyla Erişim: Parçaya Erişim, Ayrı Nesne Değil
Bir işleve Calisan& parametresiyle bir Yonetici geçirmek de aynı soruyu soruyor:
referans parametre y’den ayrı bir nesneye mi bağlanıyor, yoksa y’nin içindeki taban
parçasına mı?
// kalitim_referans.cpp — taban referansiyla erisim ayni parcaya ulasiyor
#include <iostream>
#include <string>
class Calisan {
public:
explicit Calisan(std::string ad) : ad_(ad) {}
std::string ad() const { return ad_; }
protected:
std::string ad_;
};
class Yonetici : public Calisan {
public:
Yonetici(std::string ad, int takim_no) : Calisan(ad), takim_no_(takim_no) {}
int takim_no() const { return takim_no_; }
private:
int takim_no_;
};
bool ayni_nesne_mi(const Calisan& c, const Yonetici& y) {
return static_cast<const void*>(&c) == static_cast<const void*>(&y);
}
void etiketle(const Calisan& c) {
std::cout << "referansin gordugu ad " << c.ad() << "\n";
}
int main() {
Yonetici y("Baris", 7);
etiketle(y);
std::cout << std::boolalpha;
std::cout << "referans parametrenin y ile ayni nesneyi gosterip gostermedigi "
<< ayni_nesne_mi(y, y) << "\n";
return 0;
}
referansin gordugu ad Baris referans parametrenin y ile ayni nesneyi gosterip gostermedigi true
CB2. etiketle(y) çağrısında y bir Calisan&’a bağlanıyor; ad() çağrısı
y’nin içindeki Calisan parçasının ad_ alanını okuyor ve “Baris” basıyor —
Yonetici’nin eklediği takim_no_’ya hiç erişmeden. ayni_nesne_mi(y, y) çağrısında
ilk parametre y’yi Calisan&’a, ikinci parametre aynı y’yi Yonetici&’ya
bağlıyor; iki referansın adresi karşılaştırıldığında sonuç true. Referans bağlama
burada da bir kopya kurmuyor — parametre, çağrıda adı geçen nesnenin kendisine (daha
doğrusu, o nesnenin taban parçasına) bağlanıyor. Sınıf yine parça: Calisan&
parametresinin gösterdiği şey, y’nin dışında yaşayan bir nesne değil, y’nin
içindeki taban alt nesnesi.
Taşan Bir Nesnenin Boyutu: Parça Taban Nesneyi Büyütür
Adres eşitliği taban alt nesnenin y’nin başında durduğunu gösteriyor; boyut
karşılaştırması ise taban alt nesnenin y’nin içinde, ayrı bir kutuda değil, aynı
bellek bölgesinde yer kapladığını gösteriyor.
// kalitim_boyut.cpp — turetilmis nesnenin boyutu taban parcayi icine aliyor
#include <iostream>
#include <string>
class Calisan {
public:
explicit Calisan(std::string ad) : ad_(ad) {}
protected:
std::string ad_;
};
class Yonetici : public Calisan {
public:
Yonetici(std::string ad, int takim_no) : Calisan(ad), takim_no_(takim_no) {}
private:
int takim_no_;
};
int main() {
std::cout << std::boolalpha;
std::cout << "yonetici boyutunun calisan arti en az bir int kadar olup olmadigi "
<< (sizeof(Yonetici) >= sizeof(Calisan) + sizeof(int)) << "\n";
std::cout << "yonetici boyutunun calisandan buyuk olup olmadigi "
<< (sizeof(Yonetici) > sizeof(Calisan)) << "\n";
return 0;
}
yonetici boyutunun calisan arti en az bir int kadar olup olmadigi true yonetici boyutunun calisandan buyuk olup olmadigi true
CB3. sizeof(Yonetici), Calisan alt nesnesinin tam boyutunu ve Yonetici’nin
kendi eklediği takim_no_ alanını birlikte taşıyor — iki karşılaştırma da true.
Bu, ilk iki ölçümün adres eşitliğini bellek düzeni tarafından doğruluyor: taban alt
nesne y’nin içinde ayrı, kendi başına ayrılmış bir kutuda durmuyor; y’nin fiziksel
bir bölümü olarak, Yonetici’nin kendi alanlarıyla aynı bellek bloğunun içinde yer
kaplıyor. Sınıf parça: Yonetici nesnesinin boyutu, taşıdığı Calisan parçasının
boyutunu içeriyor, ona ek bir referans ya da işaretçi olarak değil.
Genel, Korumalı ve Özel Kalıtım: Adres Aynı, Dışarı Açıklık Farklı
Üç sınıf aynı Taban’dan farklı erişim belirleyicileriyle türüyor: Genel public,
Korumali protected, Ozel private kalıtımla. Üçü de kendi içinde Taban’ın
korumalı üyesine erişebiliyor mu? Ve dışarıdan Taban*’a yalnız hangisi dönüştürülebiliyor?
// kalitim_erisim_belirleyicileri.cpp — genel korumali ozel kalitim ayni adresi tasiyor, farkli disari aciklik saglar
#include <iostream>
class Taban {
protected:
int deger_ = 42;
};
class Genel : public Taban {
public:
int oku() const { return deger_; }
};
class Korumali : protected Taban {
public:
int oku() const { return deger_; }
};
class Ozel : private Taban {
public:
int oku() const { return deger_; }
};
int main() {
Genel g;
Korumali k;
Ozel o;
std::cout << "genel kalitimda oku() " << g.oku() << "\n";
std::cout << "korumali kalitimda oku() " << k.oku() << "\n";
std::cout << "ozel kalitimda oku() " << o.oku() << "\n";
Taban* taban_g = &g;
std::cout << std::boolalpha
<< "genel kalitimda disaridan alinan taban* adresinin g ile ayni olup olmadigi "
<< (static_cast<void*>(taban_g) == static_cast<void*>(&g)) << "\n";
return 0;
}
genel kalitimda oku() 42 korumali kalitimda oku() 42 ozel kalitimda oku() 42 genel kalitimda disaridan alinan taban* adresinin g ile ayni olup olmadigi true
CB19. Üç sınıfın da oku() işlevi deger_’e sınıfın kendi gövdesi içinden erişiyor
ve üçü de 42 basıyor — erişim belirleyicisi (public, protected, private)
sınıfın kendi içindeki erişimi değiştirmiyor, çünkü protected bir üye, türeyen
sınıfın kendi gövdesinden zaten erişilebilir. Değişen şey dışarıdan görünürlük:
Taban* taban_g = &g; yalnız Genel public kalıtımla türediği için doğrudan
derleniyor — Korumali ya da Ozel’den aynı dönüştürme yapılsaydı, taban alt nesne
dışarıya Taban* olarak sunulamazdı. Adres karşılaştırması yine true: erişim
belirleyicisi, Genel’in bellek düzenini ya da taban alt nesnenin adresini
değiştirmiyor, yalnız o adrese dışarıdan hangi işaretçi tipiyle ulaşılabileceğini
sınırlıyor. Sınıf parça: oku()’nun çalıştığı deger_, hangi erişim
belirleyicisiyle türetilmiş olursa olsun, hep sınıfın kendi taban parçası — erişim
belirleyicisi bunu görünür kılıp kılmamayı seçiyor, nesneyi değiştirmiyor.
Üç Katmanlı Yapıcı Zinciri
İlk ölçüm iki katmanlı bir zinciri (Calisan → Yonetici) gösterdi. Zincir üçüncü bir
katmana (A → B → C) uzatıldığında kurma ve yıkma sırası nasıl genişliyor?
// kalitim_uc_katmanli_zincir.cpp — uc katmanli yapici zinciri en tabandan en turemise
#include <iostream>
class A {
public:
A() { std::cout << "A yapici\n"; }
~A() { std::cout << "A yikici\n"; }
};
class B : public A {
public:
B() { std::cout << "B yapici\n"; }
~B() { std::cout << "B yikici\n"; }
};
class C : public B {
public:
C() { std::cout << "C yapici\n"; }
~C() { std::cout << "C yikici\n"; }
};
int main() {
std::cout << "-- kurma --\n";
{
C c;
std::cout << "-- kapsam sonu, yikim basliyor --\n";
}
return 0;
}
-- kurma -- A yapici B yapici C yapici -- kapsam sonu, yikim basliyor -- C yikici B yikici A yikici
CB20. C c; tek bir nesne kuruyor, ama bu kurma üç aşamaya ayrılıyor: önce en
tabandaki A, sonra B, sonra C’nin kendi gövdesi — zincir kaç katmanlı olursa
olsun sıra hep en tabandan en türemişe. Yıkım, kapsam sonunda tam tersi sırayla
işliyor: önce C, sonra B, sonra A. Bu, ilk ölçümdeki iki aşamalı kurma/yıkmanın
genellemesi — C nesnesi aslında üç iç içe parçadan oluşuyor (A alt nesnesi, onun
içinde büyüyen B alt nesnesi, onun içinde büyüyen C’nin kendi alanları), ve her
parça yalnız kendinden önceki parça tamamlandıktan sonra kuruluyor. Sınıf parça:
C tek bir nesne, ama o nesnenin üç katmanı var ve her katman bir öncekinin üzerine
inşa ediliyor.
Tabana Değerle Atama Nesneyi Diliyor: Ayrı Bir Kopya
Önceki ölçümler hep işaretçi ya da referans üzerinden taban parçasına eriştiler — adres
hep aynıydı. Yonetici nesnesi Calisan tipinde değerle bir değişkene atandığında
adres de aynı mı kalıyor, yoksa bağımsız bir nesne mi ortaya çıkıyor?
// kalitim_dilimleme.cpp — tabana degerle atama nesneyi diliyor, ayri ve bagimsiz bir kopya olusuyor
#include <iostream>
#include <string>
class Calisan {
public:
explicit Calisan(std::string ad) : ad_(std::move(ad)) {}
std::string ad() const { return ad_; }
void ad_ata(std::string ad) { ad_ = std::move(ad); }
protected:
std::string ad_;
};
class Yonetici : public Calisan {
public:
Yonetici(std::string ad, int takim_no) : Calisan(std::move(ad)), takim_no_(takim_no) {}
int takim_no() const { return takim_no_; }
private:
int takim_no_;
};
int main() {
Yonetici y("Deniz", 9);
Calisan dilimlenmis = y;
std::cout << std::boolalpha;
std::cout << "dilimlenmis ile y'nin ayni adresi tasiyip tasimadigi "
<< (static_cast<void*>(&dilimlenmis) == static_cast<const void*>(&y)) << "\n";
dilimlenmis.ad_ata("Baska");
std::cout << "dilimlenmis adi degistirildikten sonra y'nin adi " << y.ad() << "\n";
std::cout << "dilimlenmis nesnenin adi " << dilimlenmis.ad() << "\n";
return 0;
}
dilimlenmis ile y'nin ayni adresi tasiyip tasimadigi false dilimlenmis adi degistirildikten sonra y'nin adi Deniz dilimlenmis nesnenin adi Baska
CB21. Calisan dilimlenmis = y; bir işaretçi ya da referans değil, yeni bir
Calisan nesnesi kuruyor — Calisan’ın kopya kurucusu y’nin taban parçasından
ad_’i kopyalıyor, Yonetici’nin eklediği takim_no_ ise hiç taşınmıyor, çünkü
Calisan’ın kendi tanımında öyle bir alan yok. Adres karşılaştırması bunu doğruluyor:
false — dilimlenmis, y’den bağımsız, kendi adresinde yaşayan ayrı bir nesne.
dilimlenmis’in adı değiştirildiğinde y’nin adı Deniz olarak kalıyor: iki nesne
birbirinden bağımsız. Bu, dersin geri kalanından farklı bir sınıf: işaretçi ve
referans erişimi hep aynı taban parçasına ulaşırken, değerle atama (dilimleme)
Calisan tipinde yeni, bağımsız bir kopya çıkarıyor — ve bu kopya Yonetici’nin
eklediği alanları taşımıyor, yalnız Calisan’ın tanımladığı kısmı.
Aşırı Yükleme Seçimi Statik Tipe Göre, Nesne Kimliği Değişmiyor
bilgi işlevi hem Calisan& hem Yonetici& için aşırı yüklenmiş. Aynı y nesnesi
bir kez Yonetici& olarak, bir kez Calisan& bir referans üzerinden bilgi’ye
geçirildiğinde, hangi aşırı yükleme seçiliyor — ve ikisi de aynı nesneyle mi çalışıyor?
// kalitim_asiri_yukleme_karsilastirmasi.cpp — asiri yukleme secimi statik tipe gore, nesne kimligi calisma zamaninda sabit
#include <iostream>
#include <string>
class Calisan {
public:
explicit Calisan(std::string ad) : ad_(std::move(ad)) {}
std::string ad_;
};
class Yonetici : public Calisan {
public:
Yonetici(std::string ad, int takim_no) : Calisan(std::move(ad)), takim_no_(takim_no) {}
int takim_no_;
};
void bilgi(const Calisan& c) {
std::cout << "bilgi(Calisan&) cagrildi, ad " << c.ad_ << "\n";
}
void bilgi(const Yonetici& y) {
std::cout << "bilgi(Yonetici&) cagrildi, ad " << y.ad_ << ", takim " << y.takim_no_ << "\n";
}
int main() {
Yonetici y("Kaan", 3);
Calisan& taban_referans = y;
bilgi(y);
bilgi(taban_referans);
std::cout << std::boolalpha
<< "iki cagrinin da ayni y nesnesiyle calisip calismadigi "
<< (static_cast<const void*>(&y) == static_cast<const void*>(&taban_referans)) << "\n";
return 0;
}
bilgi(Yonetici&) cagrildi, ad Kaan, takim 3 bilgi(Calisan&) cagrildi, ad Kaan iki cagrinin da ayni y nesnesiyle calisip calismadigi true
CB22. bilgi(y) derleme zamanında y’nin statik tipine (Yonetici) bakarak
bilgi(Yonetici&)’yi seçiyor; bilgi(taban_referans) ise taban_referans’ın statik
tipine (Calisan&) bakarak bilgi(Calisan&)‘ı seçiyor — ikisi de virtual değil, seçim
tamamen derleme zamanında, ifadenin statik tipine göre yapılıyor. Bu, M08/K02’de
ölçülen soruyla aynı eksende: hangi aşırı yüklemenin seçileceği bir derleme zamanı
kararı. Ama bu dersin ölçtüğü soru başka: seçilen işlev hangi nesneyle çalışıyor?
Adres karşılaştırması true — bilgi(Yonetici&)’nin çalıştığı y ile
bilgi(Calisan&)’ın çalıştığı taban_referans’ın gösterdiği nesne tamamen aynı.
Aşırı yükleme seçimi hangi işlevin çağrılacağını statik tipe göre belirliyor;
nesne kimliği ise bu seçimden bağımsız olarak hep aynı kalıyor. Sınıf parça: iki
farklı aşırı yükleme, aynı nesnenin taban parçasında çalışıyor.
Sınırlayıcı Ölçüm
Bu derste ölçülen kalıtım tekli ve sanal olmayan: Yonetici tek bir taban
sınıftan türedi ve hiçbir işlev virtual değildi, dolayısıyla taban alt nesnesinin
y’nin başında durduğu ve adresinin y ile çakıştığı gözlem bu koşullara bağlı —
çoklu kalıtımda (Çoklu Kalıtım ve Elmas Sorunu dersi) ikinci ve sonraki taban alt
nesneleri farklı bir ofsette durabilir, adresleri y ile çakışmayabilir. Ölçüm ayrıca
Calisan’ın Yonetici olmadan, kendi başına var olduğu durumları kapsamıyor — yalnız
bir Yonetici nesnesinin içindeki Calisan parçasını ölçtü. Bir taban işaretçisi ya da
referansı üzerinden yapılan bir çağrının hangi işlevi çalıştıracağı — çağrının statik
tipe mi, gerçek tipe mi baktığı — bu dersin konusu değil; bir sonraki ders bunu ölçüyor.
Özet
- Bir
Yoneticinesnesi kurulduğunda önceCalisanyapıcısı, sonraYonetici’nin kendi gövdesi çalışıyor — iki ayrı nesnenin kurulması değil, tek nesnenin iki aşaması. Calisan* taban = &ybir kopya çıkarmıyor;tabanileyaynı adresi taşıyor, çünkü tekli, sanal olmayan kalıtımda taban alt nesne türetilmiş nesnenin başında duruyor.- Bir
Calisan¶metresineygeçirmek de aynı adresi bağlıyor — referans bağlama bir kopya kurmuyor, sınıf parça. sizeof(Yonetici),Calisanalt nesnesinin tam boyutunu veYonetici’nin kendi eklediği alanları birlikte taşıyor; taban alt nesne ayrı bir kutuda değil, aynı nesnenin fiziksel bir bölümünde duruyor.- Yıkım sırası kurmanın tersini izliyor: önce
Yonetici’nin kendi yıkıcısı, sonraCalisan’ınki — yine tek nesnenin iki aşamalı çözülüşü.
Sonraki Adım
Bu derste taban alt nesnenin adresi hep y ile çakıştı, çünkü hiçbir işlev virtual
değildi ve çağrılacak işlev derleme zamanında, ifadenin statik tipine bakılarak
belirlendi. Bir sonraki ders, Sanal Fonksiyonlar ve Sanal Tablolar, bir taban işaretçisi
ya da referansı üzerinden yapılan bir çağrının hangi işlevi çalıştıracağını nesnenin
gerçek tipine göre nasıl belirlediğini ölçüyor — ve bu kez ölçülen adres eşitliği
değil, aynı ifadenin çalışma zamanında hep aynı nesneyle çalıştığı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.