İçeriğe geç
academia.sh

Ders 14 / 14

Tip Dönüşüm İşleçleri

static_cast, dynamic_cast, const_cast ve reinterpret_cast'ın hedef tipine göre bir referansa parça olarak mı yoksa yeni bir nesneye kopya olarak mı eriştiğinin ölçümü.

Yayın Güncelleme

Bu derste

  • static_cast ile bir taban referansına dönüşümün yeni bir nesne kurmadığını, aynı adresi parça olarak andığını ölçer
  • Aynı static_cast'ın hedef değer tipine dönüştüğünde nesneyi dilimleyerek yeni bir kopya kurduğunu gösterir
  • dynamic_cast'ın başarısız aşağı dönüşümde işaretçi için nullptr, referans için std::bad_cast verdiğini doğrular
  • const_cast ve reinterpret_cast'ın da yeni bir nesne kurmadığını, var olan nesneye farklı bir erişim yolu açtığını gösterir
İçindekiler

Önceki ders using bildiriminin ve ad alanı takma adının yeni bir sembol kurmadığını, var olan sembole yerel bir erişim yolu açtığını gösterdi — ölçü ad alanı sınırındaydı. Bu ders sınırı bir düzey daha indiriyor: tip dönüşüm işleçleri. Bir Termometre nesnesini static_cast ile tabanı Olcer’e dönüştürdüğümüzde soru aynı kalıyor — t hangi nesneyi anıyordu, dönüşüm hangi nesneyi veriyor? Cevap işlecin adına değil, hedef tipe bağlı: aynı static_cast, hedef bir referans olduğunda t’nin içindeki alt nesneyi anıyor; hedef bir değer olduğunda yepyeni bir nesne kuruyor.

Tabana Dönüştürülen Referans

Termometre, Olcer’den türüyor. t’yi const Olcer&’e static_cast ile dönüştürdüğümüzde derleyici yeni bir nesne kurmuyor; t’nin içindeki Olcer alt nesnesine daha dar bir tipten bakan bir referans üretiyor. Bu referansın adresi t’nin adresiyle aynı aralıkta kalıyor — çünkü temel sınıf alt nesnesi türetilmiş nesnenin bir parçası.

// taban-referans.cpp — static_cast ile referans donusumu yeni nesne kurar mi
#include <cstdio>

class Olcer {
public:
    virtual ~Olcer() = default;
    void oku() const { std::printf("olcer oku cagrildi\n"); }
};

class Termometre : public Olcer {
public:
    int derece() const { return derece_; }
private:
    int derece_ = 20;
};

int main() {
    Termometre t;
    const Olcer& taban = static_cast<const Olcer&>(t);

    taban.oku();
    std::printf("ayni adres mi %s\n",
        (static_cast<const void*>(&taban) == static_cast<const void*>(&t)) ? "evet" : "hayir");
    return 0;
}
olcer oku cagrildi
ayni adres mi evet

CB45. taban, t içindeki Olcer alt nesnesini anıyor; oku() çağrısı yeni bir nesne üzerinde değil, t’nin kendi parçası üzerinde çalışıyor. &taban ile &t’nin aynı adres aralığında çıkması bunu doğruluyor — dönüşüm bir kurucu çağırmadı, yalnız t’ye bakan tipi daralttı. Bu ifadenin sınıfı parça.

Değerle Dönüşüm: Aynı İşleç, Farklı Sınıf

Aynı t’yi bu kez hedefi bir referans değil bir değer olacak şekilde static_cast<Olcer>(t) ile dönüştürelim. İşleç adı aynı — static_cast — ama hedef tip artık bir referans değil, Olcer’in kendisi. Derleyici burada t’nin Olcer alt nesnesini kopyalayarak yepyeni, bağımsız bir Olcer nesnesi kuruyor.

// deger-donusumu.cpp — static_cast deger hedefte yeni nesne kurar mi
#include <cstdio>

class Olcer {
public:
    virtual ~Olcer() = default;
    void oku() const { std::printf("olcer oku cagrildi\n"); }
};

class Termometre : public Olcer {
public:
    int derece() const { return derece_; }
private:
    int derece_ = 20;
};

int main() {
    Termometre t;
    Olcer kopya = static_cast<Olcer>(t);

    kopya.oku();
    std::printf("ayni adres mi %s\n",
        (static_cast<const void*>(&kopya) == static_cast<const void*>(&t)) ? "evet" : "hayir");
    return 0;
}
olcer oku cagrildi
ayni adres mi hayir

CB46. kopya, t’den bağımsız yeni bir Olcer nesnesi; &kopya ile &t farklı adreslerde çıkıyor. Bu, önceki bölümdeki static_cast<const Olcer&>(t) ile aynı işleç olmasına rağmen tamamen farklı bir sonuç: burada t’nin Termometre’ye özgü kısmı (derece_) atılıyor, yalnız Olcer kısmı kopyalanıyor — bu, nesne dilimleme. Bu ifadenin sınıfı kopya.

Dinamik Dönüşüm: Gerçek Tipe Göre Karar

static_cast hedef tipe körü körüne güvenir; derleme anında karar verir. dynamic_cast ise çalışma anında nesnenin gerçek tipine bakar — taban referansının ardındaki nesne gerçekten istenen türetilmiş tipte değilse dönüşüm başarısız olur. İşaretçi hedefinde başarısızlık nullptr döner; referans hedefinde bir istisna fırlatır.

// dinamik-donusum.cpp — dynamic_cast basarisiz asagi donusumde ne verir
#include <cstdio>
#include <typeinfo>

class Olcer {
public:
    virtual ~Olcer() = default;
};

class Termometre : public Olcer {
public:
    int derece() const { return derece_; }
private:
    int derece_ = 20;
};

class Barometre : public Olcer {
public:
    int basinc() const { return basinc_; }
private:
    int basinc_ = 1013;
};

int main() {
    Termometre t;
    Olcer& taban = t;

    Barometre* p = dynamic_cast<Barometre*>(&taban);
    std::printf("isaretci donusumu basarili mi %s\n", p ? "evet" : "hayir");

    try {
        Barometre& b = dynamic_cast<Barometre&>(taban);
        (void)b;
        std::printf("referans donusumu yakalandi mi hayir\n");
    } catch (const std::bad_cast&) {
        std::printf("referans donusumu yakalandi mi evet\n");
    }
    return 0;
}
isaretci donusumu basarili mi hayir
referans donusumu yakalandi mi evet

CB47. taban’ın ardındaki gerçek nesne bir Termometre; Barometre değil. dynamic_cast<Barometre*> bunu çalışma anında fark edip nullptr döndürüyor — bir istisna fırlatmıyor, sadece “burada yok” diyor. Aynı başarısızlık referans hedefinde std::bad_cast fırlatıyor; burada yalnız yakalandığı yazdırılıyor, what()’in metni değil — mesajın kesin biçimi kütüphane uygulamasına bağlı, dersin ölçtüğü şey değil. p nullptr olduğu için burada üretilmiş bir nesne yok; başarısız bir dynamic_cast ne kopya ne de parça verir, elde hiçbir şey kalmaz.

Sabitliği Kaldırmak: const_cast

const_cast yalnız bir referansın ya da işaretçinin sabitlik niteliğini değiştirir; hedef tip aynı sınıf kalır, yeni bir nesne kurulmaz. Aşağıdaki gozden_gecir, kendisine const bir referansla gelen nesneyi işaretlemek için sabitliği bir kez kaldırıyor:

// const-cast.cpp — sabitligi kaldirmak yeni nesne kurar mi
#include <cstdio>

class Olcer {
public:
    void isaretle() { isaretlendi_ = true; }
    bool isaretlendi() const { return isaretlendi_; }
private:
    bool isaretlendi_ = false;
};

static void gozden_gecir(const Olcer& o) {
    const_cast<Olcer&>(o).isaretle();
}

int main() {
    Olcer o;
    gozden_gecir(o);
    std::printf("isaretlendi mi %s\n", o.isaretlendi() ? "evet" : "hayir");
    return 0;
}
isaretlendi mi evet

CB48. const_cast<Olcer&>(o), o’nun kendisine bakan, yalnız sabitlik niteliği kaldırılmış bir referans veriyor — ne kopya ne parça, o’nun kendisi. Bunun güvenli olmasının koşulu o’nun asıl nesnesinin gerçekten const olarak tanımlanmamış olması; main’deki o sabit değil, bu yüzden isaretle() çağrısı tanımsız davranışa girmiyor. Asıl const bir nesne üzerinde aynı çağrı tanımsız davranış olurdu — bu ders böyle bir çağrıyı yazmıyor da çalıştırmıyor da.

Bit Örüntüsünü Yeniden Yorumlamak: reinterpret_cast

reinterpret_cast en gevşek dönüşüm: hedef tipin kaynak tiple hiçbir ilişkisi aranmaz, yalnız aynı bit örüntüsü farklı bir tipten okunur. Garanti edilen tek şey, aynı tipe geri dönüşün orijinal değere eşit çıkması — ara değerin kendisi taşınabilir bir anlam taşımaz, bu yüzden bu ders onu yazdırmıyor, yalnız gidiş-dönüş eşitliğini ölçüyor.

// yeniden-yorumlama.cpp — reinterpret_cast gidis-donus esitligi
#include <cstdio>
#include <cstdint>

int main() {
    int deger = 42;
    int* p = &deger;

    std::uintptr_t sayisal = reinterpret_cast<std::uintptr_t>(p);
    int* geri = reinterpret_cast<int*>(sayisal);

    std::printf("gidis donus esit mi %s\n", (geri == p) ? "evet" : "hayir");
    return 0;
}
gidis donus esit mi evet

CB49. reinterpret_cast de yeni bir nesne kurmuyor; p’nin taşıdığı bit örüntüsünü önce bir tamsayı, sonra tekrar işaretçi olarak okuyor. geri == p eşitliği aynı adresin geri geldiğini gösteriyor — ama bu garanti yalnızca aynı tipe dönen gidiş-dönüş için geçerli; ara sayisal değerinin taşıdığı sayının kendisi platforma bağlı ve dersin konusu değil.

Peki bir C tarzı dönüşüm — (Olcer)t, (Olcer&)t gibi — bu dört işleçten hangisine çözülüyor? Derleyici sırayla dener: önce const_cast, sonra static_cast, sonra const_cast ile birleşmiş static_cast, en sonunda reinterpret_cast. İlk anlamlı olan seçiliyor — bu yüzden bir C tarzı dönüşümün hangi işlece karşılık geldiği, hedef tip ve kaynak tip arasındaki ilişkiye bakılarak çıkarılabilir, ama okurken bunu görmek static_cast ya da dynamic_cast yazmaktan daha zor. Bu ders C tarzı dönüşümü kullanmıyor; adını yalnız hangi işlece çözüldüğünü göstermek için anıyor.

Sınırlayıcı Ölçüm

Bu ders dört dönüşüm işlecinin dördünün de adının sınıfı belirlemediğini gösterdi — belirleyen hedef tip. Aynı static_cast, hedef bir referans olduğunda t’nin içindeki alt nesneyi anıyor (parça), hedef bir değer olduğunda yeni bir nesne kuruyor (kopya). Bu ders bu ikisini ayrı ayrı ölçtü; işlecin adına bakıp sınıfı tahmin etmek bu derste hiç yapılmadı.

Hatalı bir static_cast ile gerçekte o tipte olmayan bir nesneyi aşağı dönüştürmek — örneğin taban’ın ardındaki nesne bir Termometre iken onu static_cast<Barometre&> ile dönüştürmek — tanımsız davranıştır: derleyici bunu engellemez, çalışma anında da bir denetim yapılmaz. Bu ders böyle bir çağrıyı yazmadı da çalıştırmadı da; güvenli aşağı dönüşüm için bu dersin gösterdiği yol dynamic_cast’tır. reinterpret_cast’ın ürettiği ara değerlerin platforma bağlı yorumu, const_cast’ın gerçekten const bir nesne üzerindeki tanımsız davranışı ve C tarzı dönüşümün çözüm sırasının derleyiciler arası ayrıntıları da bu dersin dışında kaldı.

Özet

  • static_cast ile bir taban referansına dönüşüm yeni bir nesne kurmuyor; t’nin içindeki alt nesneyi daha dar bir tipten anıyor.
  • Aynı static_cast, hedef bir değer olduğunda t’yi dilimleyerek bağımsız yeni bir nesne kuruyor — işleç aynı, sınıf farklı.
  • dynamic_cast başarısız aşağı dönüşümde işaretçi için nullptr, referans için std::bad_cast veriyor; hiçbir durumda yeni bir nesne kurmuyor.
  • const_cast ve reinterpret_cast da yeni nesne kurmuyor, var olan nesneye farklı bir erişim yolu açıyor.
  • Bu dersin ölçtüğü ifade — static_cast<const Olcer&>(t) ile elde edilen taban referansı — parça sınıfındadır: t’nin kendi adresini, dar bir tipten anıyor.

Kurs Kapanışı

Bu ders, C++ Temelleri ve Nesne Tasarımı kursunun son dersiydi. Kurs boyunca her ders aynı soruyu farklı bir sınırda sordu: bir ifade hangi nesneyle çalışıyor — kendisiyle mi, bir kopyasıyla mı, bir parçasıyla mı, yoksa adı olmayan bir geçiciyle mi?

Ders Ölçülen ifade Hangi nesne Sınırlayıcı ölçüm
C ve C++ Farkları degerle aktarilan yapi kopya Çağrı noktası sınıfı söylemez; karar parametre tipinde
Sınıflar ve Erişim Denetimi uye islevin this'i kendisi private nesneyi değil sınıfın sınırını korur
Yapıcılar ve Yıkıcılar int'ten kurulan arguman geçici const referansa bağlanan geçicinin ömrü uzar
RAII koruyucunun biraktigi uye parça Kaynağa referans tutan koruyucunun sarkması yalnız anlatıldı
Kopyalama ve Atama derin kopyalanan tampon kopya Kopyanın derinliği yalnız kopyalayan yapıcının gövdesinden okunur
Referanslar referansla aktarilan yapi kendisi const referans parametre geçiciye de bağlanabilir
İşleç Aşırı Yükleme a + b sonucu geçici a + b + c’de yalnız ara sonuç geçicidir; son sonuç doğrudan toplamda kurulur
Kalıtım taban sinifin islevi parça Tekli ve sanal olmayan kalıtımla sınırlı
Sanal Fonksiyonlar ve Sanal Tablolar taban referansindan sanal cagri kendisi Değerle atama ve sanal olmayan yıkıcı kapsam dışı
Soyut Sınıflar ve Arayüzler arayuz uzerinden cagri kendisi Çoklu arayüz kalıtımı bir sonraki derse bırakıldı
Çoklu Kalıtım ve Elmas Sorunu sanal tabanin alt nesnesi parça Sanal tabana erişimin dolaylılık maliyeti ölçülmedi
İstisnalar degerle yakalanan istisna kopya catch(...) ve noexcept içinde fırlatma kapsam dışı
Ad Alanları ve Kod Düzeni using bildirimiyle anilan nesne kendisi ADL ve #include mekaniği kapsam dışı
Tip Dönüşüm İşleçleri tabana donusturulen referans parça Hatalı static_cast aşağı dönüşümü yalnız anlatıldı, çalıştırılmadı

5 kendisi · 3 kopya · 4 parça · 2 geçici; var olan 9, yeni 5

On dört ders boyunca aynı soru on dört farklı sınırda soruldu; cevap hep işlecin ya da sözdiziminin adına değil, derleyicinin gerçekte ne kurduğuna bakılarak verildi. Sıradaki kurs bu soruyu bir adım öteye taşıyor: bir nesnenin sahip olduğu kaynağı ne zaman bıraktığı, bir değeri kopyalamak yerine ne zaman taşıdığı ve standart kütüphanenin bu taşımayı nasıl kullandığı.

İ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