Ders 13 / 14
Ad Alanları ve Kod Düzeni
Aynı ada sahip iki sembolün ad alanlarıyla nasıl ayrıştığının, bir using bildiriminin ve bir ad alanı takma adının yeni bir sembol değil, var olana bir erişim yolu olduğunun ölçümü.
Yayın Güncelleme
Bu derste
- Aynı ada sahip iki farklı ad alanındaki sembolün, nitelikli adla her zaman kendi tanımına ulaştığını ölçer
- Bir using bildiriminin yeni bir sembol kurmadığını, var olan sembole yerel bir erişim yolu açtığını gösterir
- İç içe bir ad alanına iki farklı erişim yolunun (tam ad, takma ad) hep aynı değişkene ulaştığını doğrular
İçindekiler
Önceki ders HataNesnesi::kopya_sayaci’nin tek bir çeviri biriminde tanımlı static
bir üye olarak her çağrıda aynı sayacı paylaştığını gösterdi — ölçü sınıf ve nesne
sınırlarındaydı. Bu ders sınırı bir üst düzeye taşıyor: ad alanları. İki farklı
kütüphane ya da modül aynı ismi (hesapla, Arac) kullanmak isteyebilir; ad alanları
bu çakışmayı, her sembolü kendi ad alanının içine yerleştirerek çözüyor. Bu, kursun
başından beri ölçülen “hangi nesneyle çalışıyor” sorusundan farklı bir eksen: burada
nesne değil, sembolün kendisi — bir isim hangi tanıma karşılık geliyor — ölçülüyor.
Ölçü sorusu yine benzer bir kalıp izliyor — bir ifade hangi sembolle çalışıyor — ama
artık sınıf ya da nesne değil, bir isim ve onu barındıran ad alanı ölçülüyor. Kod
düzeni açısından bunun karşılığı basit: bir kütüphane, birbiriyle ilişkili adları tek
bir ad alanının altına toplayarak hem çakışmayı önlüyor hem de o adların nereden
geldiğini okuyan için belirginleştiriyor.
Aynı Ada Sahip İki Farklı Sembol: Ad Alanları Ayırıyor
kara ve deniz ad alanlarının ikisi de hesapla adında bir işlev tanımlıyor. Nitelikli
adla (kara::hesapla, deniz::hesapla) çağrıldıklarında, bunlar gerçekten iki farklı
işlev mi, yoksa bir şekilde aynı sembole mi çözülüyor?
// ad_alani_ayni_isim.cpp — ad alanlari ayni isimli iki farkli sembolu ayristiriyor
#include <iostream>
namespace kara {
int hesapla(int taban) {
return taban * 2;
}
}
namespace deniz {
int hesapla(int taban) {
return taban * 3;
}
}
int main() {
std::cout << "kara::hesapla(5) sonucu " << kara::hesapla(5) << "\n";
std::cout << "deniz::hesapla(5) sonucu " << deniz::hesapla(5) << "\n";
int (*kara_ptr)(int) = kara::hesapla;
int (*deniz_ptr)(int) = deniz::hesapla;
std::cout << std::boolalpha
<< "kara::hesapla ile deniz::hesapla ayni islev mu "
<< (kara_ptr == deniz_ptr) << "\n";
return 0;
}
kara::hesapla(5) sonucu 10 deniz::hesapla(5) sonucu 15 kara::hesapla ile deniz::hesapla ayni islev mu false
CB16. kara::hesapla(5) ve deniz::hesapla(5) farklı sonuçlar veriyor (10 ve 15) —
aynı isim, iki bağımsız gövde. kara_ptr == deniz_ptr karşılaştırması bunu işlev
işaretçisi düzeyinde doğruluyor: false, çünkü ikisi gerçekten farklı iki sembol.
Ad alanı olmasaydı, aynı imzalı iki hesapla aynı çeviri biriminde tanımlanamaz, bir
yeniden tanımlama hatası verirdi. (M11/K03’ün Kütüphane Üretimi dersinde ölçülen
static/extern bağlantı, bir sembolün bir dosyaya mı hapsolduğunu yoksa dosyalar
arasında mı paylaşıldığını belirliyordu; burada ad alanı aynı bağlantıya sahip iki
sembolü adlarıyla ayrıştırıyor — aynı sorunun farklı bir mekanizmadan çözümü.)
Sınıf kendisi: nitelikli bir ad (kara::hesapla) her zaman kendi ad alanındaki
tikel sembole ulaşıyor, başka bir ad alanındaki eş adlı sembole hiç karışmadan.
using Bildirimi Yeni Bir Sembol Kurmuyor, Var Olana Erişim Açıyor
kullan işlevi içinde using kara::hesapla; yazıldığında, ortaya çıkan yerel hesapla
adı yeni bir sembol mü, yoksa kara::hesapla’ya açılan bir erişim yolu mu?
// ad_alani_using.cpp — using bildirimi yeni sembol degil, var olana erisim yolu aciyor
#include <iostream>
namespace kara {
int hesapla(int taban) {
return taban * 2;
}
}
void kullan() {
using kara::hesapla;
int (*yerel_ptr)(int) = hesapla;
int (*dogrudan_ptr)(int) = kara::hesapla;
std::cout << std::boolalpha
<< "using ile erisilen hesapla dogrudan kara::hesapla ile ayni mi "
<< (yerel_ptr == dogrudan_ptr) << "\n";
}
int main() {
kullan();
return 0;
}
using ile erisilen hesapla dogrudan kara::hesapla ile ayni mi true
CB17. using kara::hesapla; kullan işlevi içinde hesapla adını nitelemeden
kullanmayı sağlıyor, ama işaret ettiği şey değişmiyor — yerel_ptr (nitelemeden
alınan) ile dogrudan_ptr (kara::hesapla ile doğrudan alınan) aynı işlev
işaretçisini taşıyor, true. using bildirimi bir kopya çıkarmıyor, yeni bir işlev
de kurmuyor; yalnız derleyiciye “bu nitelenmemiş ad, şu nitelenmiş sembole karşılık
gelsin” diyor. Sınıf kendisi: using sonrası nitelenmemiş çağrı, nitelenmiş
çağrıyla tam olarak aynı sembole ulaşıyor.
İç İçe Bir Ad Alanına İki Farklı Erişim Yolu
sirket::filo iç içe bir ad alanı. arac_sayisi’ne bir işlev tam nitelenmiş adla, bir
başka işlev bir ad alanı takma adıyla (namespace f = sirket::filo;) erişiyor. İkisi
de aynı değişkene mi ulaşıyor?
// ad_alani_ic_ice.cpp — tam nitelenmis ad ve takma ad ayni degiskene ulasiyor
#include <iostream>
namespace sirket::filo {
int arac_sayisi = 12;
}
void* birinci_erisim() {
return static_cast<void*>(&sirket::filo::arac_sayisi);
}
void* ikinci_erisim() {
namespace f = sirket::filo;
return static_cast<void*>(&f::arac_sayisi);
}
int main() {
std::cout << std::boolalpha
<< "tam nitelenmis adla ve takma adla erisilen degiskenin ayni olup olmadigi "
<< (birinci_erisim() == ikinci_erisim()) << "\n";
return 0;
}
tam nitelenmis adla ve takma adla erisilen degiskenin ayni olup olmadigi true
CB18. namespace sirket::filo { ... } C++17’nin iç içe ad alanı sözdizimiyle tek
satırda iki katman açıyor. birinci_erisim arac_sayisi’ne tam nitelenmiş adla
ulaşıyor; ikinci_erisim önce f’yi sirket::filo’nun bir takma adı olarak
tanımlayıp f::arac_sayisi üzerinden ulaşıyor. İki erişim yolunun döndürdüğü adresler
karşılaştırıldığında true: takma ad da, tıpkı using bildirimi gibi, yeni bir
değişken kurmuyor, var olan tek arac_sayisi’ne kısaltılmış bir yol açıyor. Sınıf
kendisi: erişim yolu ne kadar farklı görünürse görünsün (tam ad, takma ad,
using), hepsi aynı tikel sembole ulaşıyor.
Adsız Ad Alanı: Sembolü Kendi Birimine Hapsetmek
İki farklı çeviri birimi, her biri kendi adsız (anonim) ad alanı içinde, aynı isimli
(deger) bir işlev tanımlıyor. İki birim ayrı ayrı derlenip bağlandığında bu isim
çakışması bir bağlama hatası mı çıkarıyor, yoksa her birim kendi tanımını mı koruyor?
// ad_alani_ic_baglanti_birim_a.cpp — adsiz ad alani icindeki sembol bu birime ozgu, disaridan gorunmuyor
namespace {
int deger() { return 111; }
}
int birim_a_deger() {
return deger();
}
// ad_alani_ic_baglanti_birim_b.cpp — ayni isimli baska bir adsiz ad alani sembolu, farkli birimde, cakismiyor
namespace {
int deger() { return 222; }
}
int birim_b_deger() {
return deger();
}
// ad_alani_ic_baglanti_ana.cpp — iki farkli birimdeki ayni isimli adsiz ad alani sembolu cakismadan derlenip baglaniyor
#include <iostream>
int birim_a_deger();
int birim_b_deger();
int main() {
std::cout << "birim_a_deger() sonucu " << birim_a_deger() << "\n";
std::cout << "birim_b_deger() sonucu " << birim_b_deger() << "\n";
return 0;
}
birim_a_deger() sonucu 111 birim_b_deger() sonucu 222
CB41. İki birim de kendi deger()’ini çağırıyor ve her biri kendi değerini
döndürüyor — 111 ve 222, birbirine hiç karışmadan. Adsız bir ad alanı içindeki
her sembol, derleyici tarafından o çeviri birimine özgü, dışarıdan görünmeyen bir isme
dönüştürülüyor; bu yüzden iki ayrı .cpp dosyası aynı adı taşıyan bir deger()
tanımlasa bile bağlayıcı bunu bir çakışma olarak görmüyor. Bu, M11/K03’ün ölçtüğü
static bağlantıyla aynı sonucu veriyor — bir sembolü bir dosyaya hapsetmek — ama
adsız ad alanı bunu yalnız işlevler için değil, sınıflar ve değişkenler için de aynı
sözdizimiyle sağlıyor. Sınıf kendisi: birim_a_deger() içindeki deger adı,
yalnız birim_a’nın kendi tanımına ulaşıyor; birim_b’nin deger’iyle hiç
karışmıyor.
using Bildirimi Tek Sembolü Açıyor, using Yönergesi Tüm Ad Alanını
CB17 using kara::hesapla; bir bildirimdi — tek bir sembolü nitelemeden erişilebilir
kıldı. using namespace kara; bir yönerge — bütün ad alanını açar. Aynı ad
alanının iki işlevinden (hesapla, ikile) yalnız biri bildirimle içeri alınırsa,
diğerine hâlâ nitelenmiş adla mı ulaşmak gerekiyor; yönergeyle ikisine de nitelemesiz
mi ulaşılıyor?
// ad_alani_bildirim_vs_yonerge.cpp — using bildirimi tek sembolu aciyor, using yonergesi ad alaninin tamamini aciyor
#include <iostream>
namespace kara {
int hesapla(int taban) { return taban * 2; }
int ikile(int taban) { return taban * 2; }
}
void sadece_bildirim() {
using kara::hesapla; // yalniz hesapla nitelemesiz erisilebilir hale geliyor
std::cout << "sadece_bildirim: hesapla(5) = " << hesapla(5) << "\n";
std::cout << "sadece_bildirim: kara::ikile(5) = " << kara::ikile(5) << "\n";
}
void tum_ad_alani() {
using namespace kara; // kara'nin tum sembolleri nitelemesiz erisilebilir hale geliyor
std::cout << "tum_ad_alani: hesapla(5) = " << hesapla(5) << "\n";
std::cout << "tum_ad_alani: ikile(5) = " << ikile(5) << "\n";
}
int main() {
sadece_bildirim();
tum_ad_alani();
return 0;
}
sadece_bildirim: hesapla(5) = 10 sadece_bildirim: kara::ikile(5) = 10 tum_ad_alani: hesapla(5) = 10 tum_ad_alani: ikile(5) = 10
CB42. sadece_bildirim içinde hesapla(5) nitelemesiz çağrılabiliyor, ama
ikile hâlâ kara::ikile(5) diye nitelenmek zorunda — bildirim yalnız adını
verdiğiniz sembolü açıyor, kara’nın geri kalanına dokunmuyor. tum_ad_alani içinde
ise using namespace kara; sonrası hem hesapla(5) hem ikile(5) nitelemesiz
çağrılabiliyor — yönerge, ad alanının tamamını arama kapsamına ekliyor. İkisi de
aynı iki işleve ulaşıyor (her ikisi de 10 döndürüyor), fark sonuçta değil, hangi
adların nitelemesiz kullanılabilir hale geldiğinde. Sınıf kendisi: bildirim ve
yönerge farklı kapsam genişliğiyle aynı sembollere erişim açıyor, sembollerin
kendisini değiştirmiyor.
Çok Birimli Başlık/Kaynak Ayrımı: inline Aynı Tanımın Tekrarına İzin Veriyor
Bir başlık dosyasına yazılıp birden çok .cpp dosyasına dahil edilen bir işlev, o
kadar dosya sayısınca tekrar tekrar tanımlanmış olur — normal bir işlev bunu tek
tanım kuralına (ODR) aykırı bulur. inline anahtar sözcüğü bu tekrarı gerçekten
meşrulaştırıyor mu?
// ad_alani_inline_baslik_birim1.cpp — baslik dosyasindan geldigi varsayilan inline islev, bu birimde tanimlaniyor
inline int cift(int x) {
return x * 2;
}
int birim1_cift(int x) {
return cift(x);
}
// ad_alani_inline_baslik_birim2.cpp — ayni inline islevin token'i token'ina ozdes tanimi, baska bir birimde tekrarlaniyor
inline int cift(int x) {
return x * 2;
}
int birim2_cift(int x) {
return cift(x);
}
// ad_alani_inline_baslik_ana.cpp — iki farkli birimdeki ozdes inline tanim, tek tanim kuralini ihlal etmeden derlenip baglaniyor
#include <iostream>
int birim1_cift(int x);
int birim2_cift(int x);
int main() {
std::cout << "birim1_cift(21) sonucu " << birim1_cift(21) << "\n";
std::cout << "birim2_cift(21) sonucu " << birim2_cift(21) << "\n";
return 0;
}
birim1_cift(21) sonucu 42 birim2_cift(21) sonucu 42
CB43. İki ayrı çeviri birimi, cift‘i birbirinden bağımsız olarak ama
**token’i token’ına özdeş** biçimde tanımlıyor — tıpkı bir başlık dosyasının iki
.cpp’ye dahil edilmesini simüle ediyor. Program başarıyla derlenip bağlanıyor ve her
iki birim de aynı sonucu üretiyor: 42. inline, tek tanım kuralına bir istisna
açıyor: bir işlev birden çok çeviri biriminde tanımlanabilir, yeter ki her tanım
birebir aynı olsun. Bu, adsız ad alanının (CB41) çözümünün tam tersi bir strateji —
adsız ad alanı sembolü gizleyerek çakışmayı önlüyordu, inline ise sembolü
paylaşarak ve tanımların aynılığını bir sözleşme hâline getirerek önlüyor. İki
tanım birbirinden farklı olsaydı (örneğin biri x * 2, diğeri x * 3 döndürseydi),
program standarda göre biçimsiz (ill-formed) olurdu ve derleyicinin bunu bildirmesi
gerekmezdi (no diagnostic required) — derleyici bunu her zaman yakalayamaz, bu yüzden burada
hiç çalıştırılmadı; yalnız kuralına uyan, aynı tanımın tekrarlandığı durum ölçüldü.
Sınıf kendisi: cift, iki birimde de aynı sembole karşılık geliyor, birden fazla
kopya değil, tek bir sözleşmenin iki yerde tutulan aynı ifadesi.
static ile Paylaşılan (extern) Sembol Arasındaki Karşıtlık
CB41 adsız ad alanıyla iç bağlantı sağladı. static anahtar sözcüğü dosya kapsamında
aynı etkiyi doğrudan veriyor. Bir işlevi static yapmak onu gerçekten dosyaya
hapsediyor mu; buna karşılık hiç static ya da adsız ad alanı kullanılmadan tanımlanan
sıradan bir işlev (varsayılan bağlantısı extern) başka bir birimden gerçekten
çağrılabiliyor mu?
// ad_alani_baglanti_birim1.cpp — static ic baglantili sembol bu birime ozgu, extern (varsayilan) sembol disari acik
static int ic_deger() {
return 7;
}
int disa_acik_deger() {
return 700;
}
int birim1_ic_deger_kullan() {
return ic_deger();
}
// ad_alani_baglanti_birim2.cpp — ayni isimli baska bir static sembol, farkli birimde, cakismadan var oluyor
static int ic_deger() {
return 9;
}
int birim2_ic_deger_kullan() {
return ic_deger();
}
// ad_alani_baglanti_ana.cpp — static sembol her birime hapsoluyor, extern (varsayilan) sembol tum birimlerden dogrudan cagrilabiliyor
#include <iostream>
int birim1_ic_deger_kullan();
int birim2_ic_deger_kullan();
int disa_acik_deger();
int main() {
std::cout << "birim1_ic_deger_kullan() sonucu " << birim1_ic_deger_kullan() << "\n";
std::cout << "birim2_ic_deger_kullan() sonucu " << birim2_ic_deger_kullan() << "\n";
std::cout << "ana birimden dogrudan cagrilan disa_acik_deger() sonucu " << disa_acik_deger() << "\n";
return 0;
}
birim1_ic_deger_kullan() sonucu 7 birim2_ic_deger_kullan() sonucu 9 ana birimden dogrudan cagrilan disa_acik_deger() sonucu 700
CB44. birim1 ve birim2’nin kendi static ic_deger()‘leri, CB41’deki adsız ad
alanı örneğiyle birebir aynı sonucu veriyor: 7 ve 9, birbirine karışmadan —
static, dosya kapsamındaki bir işlevi de tıpkı adsız ad alanı gibi iç bağlantılı
kılıyor. Ama disa_acik_deger() hiç static ya da adsız ad alanı içinde değil;
varsayılan bağlantısı extern, ve bu yüzden yalnız bildirilerek (int disa_acik_deger();) ana birimden doğrudan çağrılabiliyor — 700, kendi
biriminde tanımlanan tek nesneye ulaşıyor. Üç mekanizmanın üçü de bu derste ölçüldü:
adsız ad alanı (CB41) ve static (burada) sembolü dosyaya hapsediyor, inline
(CB43) sembolü paylaşıp tanımların aynı kalmasını zorunlu kılıyor, sıradan extern
bağlantı ise sembolü tek bir tanımda toplayıp her yerden erişilebilir kılıyor. Sınıf
kendisi: her üç mekanizma da aynı isim çakışması sorununu, sembolün görünürlük
kapsamını değiştirerek çözüyor; sembolün kendisi hep tikel kalıyor.
Sınırlayıcı Ölçüm
Bu derste ölçülen çok birimli örnekler, gerçek bir başlık dosyasını (.h) birden
çok .cpp’ye #include etmek yerine, aynı tanımı elle iki kaynak birimine
kopyalayarak simüle etti — önişlemcinin #include sırasında yaptığı metinsel
kopyalamanın kendisi bu derste ayrıca ölçülmedi. Argümana bağlı arama (ADL, std::cout << gibi bir işleci niteleme yazmadan bulan mekanizma) bu dersin kapsamı dışında;
burada her çağrı ya nitelenmiş adla ya da açık bir using ile yapıldı. Tek tanım
kuralının ihlal edildiği durum — iki birimde birbirinden farklı, inline
olmayan iki tanımın aynı dışa açık isimle çakışması — bağlama hatası ya da tanımsız
davranışla sonuçlanacağı için hiç çalıştırılmadı; yalnız kurala uyan durumlar
ölçüldü. Bir ad alanının kendisinin (işlevlerinden bağımsız olarak) birden çok
dosyada namespace kara { ... } bloklarıyla genişletilmesi de ayrıca gösterilmedi;
burada ad alanları hep tek dosya içinde tanımlı kaldı, yalnız onların içindeki
sembollerin bağlantı türü çok birimli test edildi.
Özet
- İki farklı ad alanındaki aynı isimli işlevler bağımsız sembollerdir; nitelikli adla
(
kara::hesapla) her zaman kendi tanımına ulaşılıyor. - İşlev işaretçisi karşılaştırması bunu doğruluyor:
kara::hesaplailedeniz::hesaplaaynı işlev değil. - Bir
usingbildirimi yeni bir sembol kurmuyor; nitelenmemiş adı var olan nitelikli sembole bağlıyor — sınıf kendisi. - Bir ad alanı takma adı (
namespace f = sirket::filo;) da aynı ilkeyle çalışıyor: yeni bir değişken kurmuyor, var olana kısaltılmış bir erişim yolu açıyor. - Tam nitelenmiş ad,
usingve takma ad — üçü de aynı tikel sembole ulaşıyor; farklı olan yalnız yazım biçimi.
Sonraki Adım
Bu ders boyunca static_cast<void*>(...) sıkça kullanıldı, ama static_cast’in ne
garanti ettiği ya da bir sınıfın kendi dönüşüm kurallarını nasıl tanımlayabileceği hiç
açılmadı — yalnız adres karşılaştırmak için bir araç olarak kullanıldı. Kursun kapanış
dersi, Tip Dönüşüm İşleçleri, bunu doğrudan ele alıyor: bir sınıfın kendi tipinden başka
bir tipe nasıl örtük ya da açık bir dönüşüm sunabileceğini, ve böyle bir dönüşüm
çağrıldığında ortaya çıkan değerin özgün nesneyle mi, yoksa ondan türetilmiş yeni bir
nesneyle mi çalıştığını ölçüyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.