Ders 02 / 14
Sınıflar ve Erişim Denetimi
Bir üye işlevin hangi nesneyle çalıştığını ve erişim denetiminin nesne değil sınıf düzeyinde işlediğini ölçer.
Yayın Güncelleme
Bu derste
- Bir üye işlev çağrısında this işaretçisinin hangi nesneyi gösterdiğini ölçer
- class ile struct'ın varsayılan erişim farkını ve public/private/protected'i uygular
- Erişim denetiminin nesne değil sınıf düzeyinde çalıştığını koşturarak gösterir
İçindekiler
Önceki ders bir serbest işlevin parametresine bakarak hangi nesneyle çalıştığını ölçtü:
kaydir(Nokta n) kendi kopyasıyla çalışıyordu, tasi(Nokta* p) çağıranın nesnesine
adresle ulaşıyordu. Bir üye işlevin de bir parametresi vardır, ama derleyici onu örtük
geçirir: çağrının yapıldığı nesnenin adresi. Bu örtük parametrenin adı this’tir ve bir üye
işlevin gövdesinde her zaman kullanılabilir — yazılmasa bile oradadır. Bu ders o örtük
parametreyi aynı çekirdekle ölçer: bir üye işlev çağrıldığında this hangi nesneyi
gösteriyor, ve bu bilgi erişim denetimiyle nasıl bir araya geliyor?
Sınıf ve Erişim Denetimi
class ile struct C++‘ta aynı şeydir; ikisi de üye değişken ve üye işlev taşıyabilen bir
tür tanımlar, ikisi de kalıtım alabilir, ikisi de yapıcı ve yıkıcı çalıştırır. Aralarındaki
tek fark varsayılan erişimdir. struct üyeleri varsayılan olarak public, class üyeleri
varsayılan olarak private‘tır — başka hiçbir davranış farkı yoktur. Bu, C’den gelen bir
mirastır: C’de struct yalnız veri taşıyan bir kayıttı, işlev taşımazdı; C++ struct’a üye
işlev ekleme yeteneğini verirken geriye dönük uyumluluk için varsayılan erişimi public
bıraktı. Bir C++ kod tabanında struct’ın genellikle yalnız veri taşıyan, davranışı az
türler için, class’ın ise davranışı erişim denetimiyle koruyan türler için kullanılması bir
gelenektir — dilin kendisi bunu zorlamaz.
Bir sınıf üç erişim düzeyi bildirir:
public— sınıfın dışından, herhangi bir kodun herhangi bir yerinden erişilebilir. Bir sınıfın dışarıya sunduğu arayüz buradadır.private— yalnız sınıfın kendi üye işlevlerinden (vefriendolarak bildirilmiş işlev ya da sınıflardan) erişilebilir. Bir sınıfın kendi iç durumunu tuttuğu veri üyeleri genellikle buradadır.protected— kendi üye işlevlerinden ve türeyen sınıflardan erişilebilir;private‘tan farkı yalnız kalıtımda ortaya çıkar. Kalıtım M11/K04’ün ikinci konusuna kadar bu kursta kullanılmaz, bu yüzdenprotectedburada yalnız adıyla anılır.
Bir erişim düzeyi bildirimi (public:, private: ya da protected:) bir sınıf gövdesinde
o noktadan sonraki tüm üyelere uygulanır, ta ki bir sonraki erişim düzeyi bildirimine kadar.
Hesap sınıfında public: altındaki üç üye işlev dışarıdan çağrılabilir, private: altındaki
bakiye_ çağrılamaz — aynı sınıf gövdesinde birden çok public:/private: bloğu da
bulunabilir, derleyici hepsini birleştirir.
Bir sınıf veri üyesi (nesnenin her örneğinde ayrı bir kopyası olan değişken — bakiye_
her Hesap nesnesinde kendi değerini taşır) ve üye işlev (sınıfın tüm örnekleri arasında
paylaşılan, çağrıldığı örneğin veri üyelerine this üzerinden erişen kod — yatir’in
makine kodu bir kez üretilir, her Hesap nesnesi için ayrı ayrı değil) taşır. Bir üye işlev
const işaretliyse this’in kendisi const’tur: işlev veri üyelerini değiştiremez, yalnız
okuyabilir. bakiye() ve esit_mi bu yüzden const’tur — ikisi de bakiye_’yi okur ama
değiştirmez; yatir const değildir çünkü bakiye_’yi değiştirir. Bir const nesne
üzerinden yalnız const üye işlevler çağrılabilir; const olmayan bir üye işlevi const
bir nesne üzerinden çağırmak derleme hatasıdır, çünkü derleyici this’in işlev içinde
değiştirilmeyeceğini garanti edemez.
friend bildirimi bir işleve ya da sınıfa, üyesi olmadığı bir sınıfın private üyelerine
erişim tanır — erişim denetiminin tek istisnasıdır ve sınıfın kendi bildirimiyle açıkça
verilir, dışarıdan alınamaz. Bu kursta friend yalnız bir kez, operator<<’ın bir sınıfın
private veri üyesini okuyabilmesi için kullanılır (İşleç Aşırı Yükleme dersinde); burada
yalnız adıyla anılır.
Kapsülleme — bir sınıfın iç durumunu dışarıdan gizleyip yalnız işlevler üzerinden erişim
sunması — M08’de zaten işlendi; burada yeniden öğretilmez, yalnız C++’ın public/private
sözdizimiyle uygulanır. Hesap bu fikri uygular: bakiye_’ye dışarıdan
doğrudan yazılamaz, yalnız yatir üzerinden değiştirilebilir.
Üye İşlevin this’i
this, bir üye işlevin gövdesinde örtük olarak tanımlı bir işaretçidir; tipi çağrıldığı
sınıfa göre belirlenir. Hesap::yatir içinde this’in tipi Hesap* const’tur — işaretçinin
kendisi değiştirilemez (başka bir Hesap’ı göstermeye başlayamaz), ama gösterdiği nesne
değiştirilebilir. const işaretli bir üye işlevde — Hesap::bakiye() const gibi — this’in
tipi const Hesap* const’tur: hem işaretçi hem gösterdiği nesne değiştirilemez, bu yüzden
const bir üye işlev veri üyelerine yalnız okuma erişimi kazanır. Kod bu işaretçiyi elle
yazmaz — bakiye_ += miktar derleyici tarafından this->bakiye_ += miktar olarak okunur —
ama aşağıdaki ölçüm this’in gerçekten çağrılan nesneyi gösterdiğini, başka bir nesneyi
değil, açıkça gösterir: h.yatir(50) çağrısından önce alınan &h adresiyle, işlev içinde
üstü kapalı kullanılan this’in eşdeğeri karşılaştırılır.
// hesap.hpp — bakiyesi private olan bir hesap
#ifndef HESAP_HPP
#define HESAP_HPP
class Hesap {
public:
void yatir(int miktar) { bakiye_ += miktar; }
int bakiye() const { return bakiye_; }
bool esit_mi(const Hesap& digeri) const { return bakiye_ == digeri.bakiye_; }
private:
int bakiye_ = 0;
};
#endif
// this-olcumu.cpp — uye islevin this'i cagrilan nesneyi mi gosteriyor
#include <cstdio>
#include "hesap.hpp"
int main() {
Hesap h;
Hesap* onceki = &h;
h.yatir(50);
std::printf("this cagrilan nesneyi gosteriyor mu %s\n", onceki == &h ? "evet" : "hayir");
std::printf("bakiye %d\n", h.bakiye());
return 0;
}
this cagrilan nesneyi gosteriyor mu evet bakiye 50
SK3. h.yatir(50) çağrısında derleyici this’e &h’yi geçirir; yatir içindeki
bakiye_ += miktar satırı h.bakiye_’yi değiştirir, başka hiçbir Hesap’ı değil. Bu ifade
kendisi sınıfındadır: h’nin kendisiyle çalışır.
Erişim Denetimi Nesneyi Değil Sınıfı Sorar
bakiye_ private’tır; Hesap’ın dışından h.bakiye_ yazılamaz — main içinde böyle bir
satır denense derleyici reddeder, çünkü main Hesap sınıfının bir üyesi değildir. Ama
Hesap’ın kendi bir üye işlevi, başka bir Hesap nesnesinin bakiye_’sine doğrudan
erişebilir — erişim denetimi nesneyi değil, sınıfı sorar. esit_mi bunu gösterir:
digeri.bakiye_ çağıranın kendi bakiye_’si değildir, ama aynı sınıftan olduğu için
derlenir. Bu, birçok dilde geçerli olmayan bir C++ kuralıdır — bazı dillerde erişim denetimi
nesne düzeyinde işler ve bir nesne yalnız kendi verisine dokunabilir; C++’ta sınır sınıfın
kendisidir, tek bir nesne değil.
// esitlik-olcumu.cpp — bir uye islev baska bir nesnenin private uyesine erisiyor
#include <cstdio>
#include "hesap.hpp"
int main() {
Hesap a;
Hesap b;
a.yatir(30);
b.yatir(30);
std::printf("a ile b esit mi %s\n", a.esit_mi(b) ? "evet" : "hayir");
b.yatir(1);
std::printf("a ile b esit mi %s\n", a.esit_mi(b) ? "evet" : "hayir");
return 0;
}
a ile b esit mi evet a ile b esit mi hayir
SK4. a.esit_mi(b) içindeki digeri.bakiye_ erişimi b’nin private üyesine
a’nın üye işlevinden yapılır; derleyici bunu yalnız digeri’nin tipine (const Hesap&)
bakarak kabul eder, a ile b’nin aynı nesne olup olmadığına bakmaz. Erişim kararı
derleme zamanında, sınıf bilgisine göre verilir.
Bu, birçok C++ öğrencisinin ilk şaşırdığı noktalardan biridir: private’ın “yalnız bu nesne
kendi verisine erişebilir” anlamına geldiği sanılır, ama kural budur — “yalnız bu sınıfın
üye işlevleri erişebilir.” esit_mi’nin gövdesi derlenirken derleyici yalnız şunu bilir:
digeri bir const Hesap&’tır, ve o an derlenen işlev Hesap sınıfının bir üyesidir; bu iki
bilgi digeri.bakiye_ erişimine izin vermek için yeterlidir. a ile digeri’nin (yani
b’nin) aynı çalışma zamanı nesnesi olup olmadığı derleme zamanında bilinmez ve erişim
kararına hiç katılmaz — nitekim ikinci çağrıda a ile b ayrı nesnelerdir ve erişim yine de
derlenir.
private Neden Nesneyi Değil Sınıfı Korur
Bu kural rastgele değildir; sınıf yazarının değişmezlerini (invariant — bir nesnenin her
zaman doğru kalması gereken durumu, örneğin bakiye_’nin hiçbir zaman negatife düşmemesi)
korumasını mümkün kılar. Eğer private yalnız “başka nesnelerden değil, kendi nesnenden
erişilebilir” anlamına gelseydi, esit_mi gibi iki nesneyi karşılaştıran ya da bir nesneden
diğerine veri kopyalayan işlevler yazılamazdı — her üye işlev yalnız kendi this’ine
bakabilirdi. Sınıf düzeyinde erişim, bir sınıfın kendi üye işlevleri arasında güvenle veri
paylaşmasını sağlarken, sınıfın dışındaki koda aynı veriyi kapatır. Sınırı çizen nesne değil,
sınıfın kaynak dosyasındaki bildirimdir.
Bunun karşıtı, alanı dışarıya açık bırakılmış bir yapıdır: böyle bir kayıtta çağıran kod o
alanı herhangi bir denetimden geçirmeden değiştirebilir. Hesap aynı hatayı yapmaz —
bakiye_’ye dışarıdan yazma yolu yoktur, yalnız yatir üzerinden değiştirilebilir. yatir
bugün bakiye_ += miktar dışında bir şey yapmıyor, ama private sayesinde ileride negatif
bir miktar’ı reddetmek ya da bir üst sınırı denetlemek gibi bir kural eklense, bu kural
her çağıranı değiştirmeden yalnız yatir’in içine yazılabilir — çünkü bakiye_’ye giden
tek yol yatir’dir. Bu, private’ın sağladığı asıl mühendislik değeridir: sınıfın iç
temsilini değiştirme özgürlüğü, dışarıdaki koda dokunmadan.
const Nesne Üzerinden Çağrı Seçimi
bakiye() ve esit_mi neden const işaretlidir, yatir neden değildir sorusunun bir
pratik sonucu vardır: bir const Hesap& parametre alan bir işlev, o parametre üzerinden
yalnız const üye işlevleri çağırabilir. esit_mi’nin imzası bool esit_mi(const Hesap& digeri) const’tur; gövdesi içinde digeri.bakiye_’ye doğrudan erişir, ama digeri.bakiye()
çağırsaydı da derlenirdi — çünkü bakiye() const’tur ve const bir referans üzerinden
çağrılabilir. digeri.yatir(10) denenmiş olsaydı derlenmezdi: yatir const değildir, ve
const bir nesne (ya da const bir referansın gösterdiği nesne) üzerinden const olmayan
bir üye işlev çağrılamaz. Bu kural, bir işlevin imzasına const yazmasının yalnız belgeleyici
bir not olmadığını, derleyicinin gerçekten denetlediği bir sözleşme olduğunu gösterir:
esit_mi’nin digeri’yi değiştirmeyeceği, işlevin kendi imzasından derleme zamanında
doğrulanır.
Aynı kural this için de geçerlidir. Hesap::esit_mi const işaretli olduğu için kendi
this’i de const Hesap* const’tur; bu işlevin gövdesi içinde bakiye_ += 1 gibi bir
değişiklik denense — kendi nesnesi üzerinde bile — derleyici reddeder. const bir üye
işlev, ne kendi nesnesini ne de const bir referansla aldığı başka bir nesneyi
değiştirebilir; değiştirebileceği tek şey yerel değişkenler ve mutable işaretli veri
üyeleridir (mutable, önbellekleme gibi nadir durumlar için ayrılmış bir işaretleyicidir ve
bu kursta kullanılmaz).
Bildirim Sırası ve Erişim Düzeyi Blokları
Hesap sınıfında erişim düzeyi bildirimleri belli bir yerleşim sırası izler: önce public:
altında dışarıya sunulan üye işlevler, sonra private: altında veri üyesi. Bu sıra dilin bir
kuralı değildir — derleyici için private: üstte, public: altta olsaydı da aynı sonucu
verirdi — ama okunabilirlik için yaygın bir alışkanlıktır: bir sınıfı okuyan kişi önce onun
sunduğu arayüzü, sonra iç temsilini görür. Ters sıra (önce private veri, sonra public
işlevler) de geçerlidir ve bazı kod tabanlarında tercih edilir; bu ders public-önce
sırasını izler ve kursun geri kalanında tutarlı kalır.
Bir erişim düzeyi bildirimi yalnız bir kez kullanılmak zorunda değildir — aynı sınıf gövdesi
içinde public: ve private: birden fazla kez tekrarlanabilir, her biri kendinden sonraki
bloğu kapsar. Hesap bunu yapmaz; sınıf küçük olduğu için tek bir public: ve tek bir
private: yeterlidir. Daha büyük bir sınıfta, ilgili üyeleri bir arada tutmak için birden
çok blok kullanmak — örneğin yapıcılar için ayrı bir public:, sorgulayıcı işlevler için
ayrı bir public: — okunabilirliği artırabilir; bu bir stil tercihidir, derleyicinin
davranışını değiştirmez.
Sınırlayıcı Ölçüm
Bu dersin ölçtüğü ifade kendisi sınıfındadır: h.yatir(50) h’nin kendisine yazar. Ama
erişim denetiminin sınırı da burada görülür: private bir üyeye “kimin eriştiği” nesne
düzeyinde değil sınıf düzeyinde sorulur. esit_mi içindeki digeri.bakiye_ çağrısı bunu
kanıtlar — aynı sınıftan farklı bir nesnenin private üyesine, o nesnenin kendi işlevi
olmadan, yalnız aynı sınıfın bir başka üye işlevinden erişilebilir. private, nesneyi değil
sınıfın sınırını korur.
Bu sınırın bir sonucu daha vardır: private bir üyeye erişim, sınıfın kendi kaynak
kodundaki her üye işlevden mümkündür — bu üye işlevlerin sayısı sınıf büyüdükçe artabilir ve
her biri aynı değişmezi bozma riski taşır. Hesap üç üye işlevle küçüktür ve hepsi bu
derste görülebilir; büyük bir sınıfta private veriye erişen onlarca üye işlev olabilir, ve
bu derste ölçülen kural — erişimin nesneye değil sınıfa bağlı olması — o ölçekte de
değişmeden kalır. private, “kaç yerden erişilebilir” sorusuna nesne sayısıyla değil, sınıfın
üye işlev sayısıyla cevap verir.
Bu iki nokta — hangi ifadenin hangi nesneyle çalıştığı ve erişim denetiminin nesneyi değil
sınıfı sorduğu — birlikte okunduğunda bir sınıfın neden güvenilir bir sınır çizebildiğini
açıklar. this her çağrıda doğru nesneyi gösterir, bu yüzden bir üye işlev kendi nesnesini
başka bir nesneyle karıştırmaz; private her üye işleve aynı sınıftan gelen her nesneye
erişim tanır, bu yüzden sınıf kendi içinde veri paylaşımı gerektiren işlemleri (karşılaştırma,
kopyalama, birleştirme) yazabilir. Bu iki kural bir arada, bir sonraki dersin konusu olan
yapıcıya zemin hazırlar: bir yapıcı da bir üye işlevdir, kendi this’i vardır, ve private
veri üyelerine aynı serbestlikle erişir — ama onu farklı kılan, çağrıldığı nesnenin henüz
var olmayan bir nesne olmasıdır.
Özet
- Bu dersin ölçtüğü ifade kendisi sınıfındadır:
h.yatir(50)çağrısındathis,&h’ye eşittir. classilestruct’ın tek farkı varsayılan erişimdir;public/private/protectedüç erişim düzeyini bildirir.constişaretli bir üye işlevdethisdeconst’tur; veri üyeleri değiştirilemez.- Erişim denetimi derleme zamanında ve sınıf düzeyinde çalışır: bir üye işlev, aynı
sınıftan başka bir nesnenin
privateüyesine erişebilir. - Kapsülleme M08’de tanımlandı; burada yalnız
class/privatesözdizimiyle uygulandı.
Sonraki Adım
Bu ders bir üye işlevin hangi nesneyle çalıştığını gösterdi; bir sonraki ders aynı soruyu
bir yapıcıya sorar — int’ten örtük kurulan bir argümanın adsız, geçici bir
nesne olduğunu ve bu nesnenin tam olarak ne zaman yok olduğunu ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.