İçeriğe geç
academia.sh

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::hesapla ile deniz::hesapla aynı işlev değil.
  • Bir using bildirimi 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, using ve 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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat