İçeriğe geç
academia.sh

Ders 11 / 11

Tip Çıkarımı

auto'nun const ve referansı düşürmesi, auto&/const auto&/auto&&, decltype ve decltype(auto), yapısal bağlamada kopya-referans farkı ve tip çıkarımının kaynağa ne bıraktığı.

Yayın Güncelleme

Bu derste

  • auto'nun bir ifadenin const'unu ve referansını nasıl düşürdüğünü açıklar.
  • auto&, const auto& ve auto&& arasındaki farkı kaynağa etkisiyle birlikte ayırt eder.
  • decltype ile decltype(auto)'nun tip çıkarımını farklı kurallarla yaptığını gösterir.
  • Yapısal bağlamada kopya ile referansın kaynağa farklı sonuç bıraktığını doğrular.
İçindekiler

Önceki ders bir görünümün lvalue’dan kurulunca kapsayıcıyı aynen bıraktığını, rvalue’dan kurulunca taşıyan yapıcının kapsayıcıyı boşa çevirdiğini gösterdi. Her iki örnekte de auto bir yerde duruyordu — görünümün gerçek tipini yazmak yerine derleyiciye bıraktık. Bu ders o kısayolun ne yaptığını açıyor: auto bir ifadenin tipini nasıl çıkarır, hangi durumda const’u ve referansı düşürür, hangi yazımla düşürmez. Kursun ölçtüğü soru burada da aynı: bir tip çıkarımı, kaynağa ne bırakır?

auto Neyi Atar, Neyi Düşürür

auto bir başlatıcı ifadesinden tip çıkarır; ama çıkardığı tip, ifadenin tipiyle birebir aynı değildir. Kural şu: auto kendi başına yazıldığında ifadenin const’unu ve referansını düşürür, geriye yalnız temel tip kalır. Aşağıdaki örnek bunu gösteriyor.

// auto-dusurme.cpp — auto const ve referansi nasil dusuruyor
#include <cstdio>

int main() {
    const int sabit = 42;
    int deger = 10;
    int& referans = deger;

    auto a = sabit;      // int -- const dustu
    auto b = referans;   // int -- referans dustu

    a = 99;
    b = 99;

    std::printf("sabit %d, deger %d\n", sabit, deger);
    std::printf("a %d, b %d\n", a, b);
    return 0;
}
sabit 42, deger 10
a 99, b 99

a ve b, sabit ile referansın birer kopyasıdır; ikisine de yazmak kaynağı etkilemez. sabit 42’de, deger 10’da kalıyor. KT39. auto, bir ifadenin const’unu ve referansını düşürerek bağımsız bir kopya üretir; kopyaya yazmak kaynağa hiçbir şey bırakmaz — kaynak aynen kalır, çünkü kopyanın kendisi ayrı bir nesnedir.

auto& ile Referansa Bağlanmak

auto& yazıldığında referans düşmez; çıkarılan tip başlatıcının kendisine bağlanan bir referanstır. Bu, auto’nun tam tersi bir davranış: kopya değil, kaynağın kendisi.

// auto-referans.cpp — auto& bir kopyaya degil kaynagin kendisine baglanir
#include <cstdio>

int main() {
    int deger = 10;
    int& referans = deger;

    auto& baglanti = referans;   // int& -- referans dusmuyor
    baglanti = 99;

    std::printf("deger %d\n", deger);
    return 0;
}
deger 99

baglanti üzerinden yapılan atama degere ulaşıyor; auto&, referansın referans olduğunu unutmadı. auto ile auto& arasındaki tek fark bir & işareti, ama sonuç tersine dönüyor: biri kopya kurar, öteki kaynağın kendisine bağlanır.

const auto&: Kopyasız Salt Okuma

Bir kapsayıcıyı yalnız okumak için gezerken auto her elemanı kopyalar. Eleman büyükse (bir std::string, bir kayıt) bu kopyalama gereksiz bir maliyettir. const auto&, kopya almadan kaynağın kendisini salt okunur biçimde bağlar.

// const-auto-referans.cpp — buyuk elemani kopyalamadan okumak
#include <cstdio>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> stok{"elma", "armut", "kiraz"};

    for (const auto& ad : stok) {
        std::printf("%s\n", ad.c_str());
    }

    std::printf("eleman sayisi %zu\n", stok.size());
    return 0;
}
elma
armut
kiraz
eleman sayisi 3

Döngü boyunca hiçbir std::string kopyalanmadı; ad her adımda stokun o anki elemanına bağlandı. const derleyiciye bu bağlantı üzerinden yazma yapılmayacağını söylüyor, stok döngü sonunda aynen kalıyor. Kopya almamak burada bir performans kararı, kaynağa etkisi değişmiyor: const auto& de, kopyalayan auto da salt okuma sırasında kaynağı bozmaz — fark, ara kopyanın var olup olmamasında.

auto&&: İletme Referansı

auto&& yazıldığında çıkarılan tip başlatıcının değer kategorisine göre değişir: bir sol değerle başlatılırsa sıradan bir referansa (T&), bir sağ değerle başlatılırsa bir sağ değer referansına (T&&) döner. Bu ikili davranışı gösteren auto&& biçimine iletme referansı denir.

// auto-iletme-referansi.cpp — auto&& deger kategorisine gore farkli tip cikarir
#include <cstdio>
#include <string>

int main() {
    std::string ad = "elma";

    auto&& sol_deger_baglantisi = ad;                    // string&
    auto&& sag_deger_baglantisi = std::string("armut");  // string&&

    sol_deger_baglantisi += " (guncellendi)";

    std::printf("ad: %s\n", ad.c_str());
    std::printf("sag deger: %s\n", sag_deger_baglantisi.c_str());
    return 0;
}
ad: elma (guncellendi)
sag deger: armut

sol_deger_baglantisi, adın kendisine bağlandığı için üzerine yapılan ekleme ade ulaşıyor. sag_deger_baglantisi ise geçici bir nesneye bağlandı ve onun ömrünü ifadenin sonuna kadar uzattı. İletme referansı yalnız auto&& ya da bir şablon parametresi T&& biçiminde çıkarımla ortaya çıkar; elle yazılmış std::string&& bu davranışı göstermez, çünkü tipi zaten sabittir, çıkarılmaz.

decltype ve decltype(auto)

decltype, autodan farklı bir kurala uyar: bir ifadenin tipini, kaynağını değiştirmeden olduğu gibi verir. auto referansı düşürürken decltype düşürmez.

// decltype-ornek.cpp — decltype referansi dusurmez
#include <cstdio>

int main() {
    int deger = 10;
    int& referans = deger;

    decltype(deger) a = 1;        // int
    decltype(referans) b = deger; // int& -- referans dusmuyor

    b = 42;

    std::printf("deger %d, a %d\n", deger, a);
    return 0;
}
deger 42, a 1

b, decltype(referans) sayesinde int& tipini aldı ve degerin kendisine bağlandı; a ise sıradan bir int kopyası. auto burada ikisini de inte indirgerdi.

decltype(auto), bir işlevin dönüş tipini decltype kurallarıyla çıkarmak için kullanılır — ama bir tuzak taşır: dönüş ifadesinin parantez içine alınması, çıkarılan tipi değiştirir. Aşağıdaki iki işlev aynı değişkeni döndürüyor gibi görünür, ama biri değer, öteki referans çıkarır.

// decltype-auto-parantez.cpp — parantez ifadeyi sol degere cevirip tipi degistirir
#include <cstdio>
#include <type_traits>

int sayac = 0;

decltype(auto) artir_ve_don() {
    ++sayac;
    return sayac;     // decltype(auto) -> int (degisken adi tek basina)
}

decltype(auto) artir_ve_don_parantezli() {
    ++sayac;
    return (sayac);   // decltype(auto) -> int& (parantezli ifade bir sol deger)
}

int main() {
    constexpr bool ilki_deger = std::is_same_v<decltype(artir_ve_don()), int>;
    constexpr bool ikincisi_referans =
        std::is_same_v<decltype(artir_ve_don_parantezli()), int&>;

    std::printf("ilki deger mi: %s\n", ilki_deger ? "evet" : "hayir");
    std::printf("ikincisi referans mi: %s\n", ikincisi_referans ? "evet" : "hayir");
    return 0;
}
ilki deger mi: evet
ikincisi referans mi: evet

decltype(auto), return sayac; için tek başına bir değişken adı görüp int çıkarır. return (sayac); için ise parantezli ifadeyi bir sol değer olarak okur ve int& çıkarır. İki işlev de burada sayac’ın kendisi genel kapsamda tanımlı olduğu için güvenle çalışıyor; ama işlev içinde tanımlı yerel bir değişkeni aynı şekilde parantezle döndürmek, işlev bittiğinde yok olan bir nesneye referans üretir — çağıran taraf bu referansı okumadan önce bile tanımsız davranışa girilmiş olur. Bu yüzden decltype(auto) dönüşünde parantez, göz ardı edilebilecek bir yazım tercihi değildir.

Yapısal Bağlama: Kopya mı, Referans mı

Yapısal bağlama bir kayıt tipini (bir std::pair, bir std::map’in elemanı) tek satırda adlandırılmış parçalara ayırır: auto [ad, adet] : stok. Buradaki auto da aynı kuralı izler — kaynağı kopyalar, referans istiyorsan & eklemen gerekir. Sırasız bir kapsayıcının gezinme sırası tanımsız olduğundan aşağıdaki örnek std::map kullanıyor; anahtara göre sıralı olduğu için çıktısı kararlıdır.

// yapisal-baglama.cpp — kopya ile referansli yapisal baglamanin farki
#include <cstdio>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stok{{"elma", 4}, {"armut", 2}};

    for (auto [ad, adet] : stok) {
        adet += 100;   // yalnizca kopyayi degistiriyor
    }

    std::printf("kopyadan sonra:");
    for (const auto& [ad, adet] : stok) {
        std::printf(" %s=%d", ad.c_str(), adet);
    }
    std::printf("\n");

    for (auto& [ad, adet] : stok) {
        adet += 100;   // kaynagin kendisini degistiriyor
    }

    std::printf("referanstan sonra:");
    for (const auto& [ad, adet] : stok) {
        std::printf(" %s=%d", ad.c_str(), adet);
    }
    std::printf("\n");
    return 0;
}
kopyadan sonra: armut=2 elma=4
referanstan sonra: armut=102 elma=104

İlk döngü auto [ad, adet] ile her elemanın kopyasını bağladı; adet += 100 yalnız o kopyaya yazdı, stok kopyadan sonra satırında değişmeden görünüyor. İkinci döngü auto& [ad, adet] ile elemanların kendisine bağlandı; aynı += 100 bu kez stokun kendisine ulaştı. KT40. Yapısal bağlamada da tip çıkarımının sınıfı köşeli parantezden önceki &e bağlıdır: auto [...] kaynağı aynen bırakır, auto& [...] kaynağı değiştirir — ikisi de aynı kayıt tipini, aynı isimlerle parçalıyor, fark yalnız o tek karakterde.

Okunabilirlik Dengesi

auto bir kısayoldur ama her yerde okunabilirliği artırmaz. Tip adı okura kaynağın ne olduğunu, hangi işlemlerin geçerli olduğunu söylüyorsa yazılmalıdır: bir yineleyicinin uzun şablon adı yerine auto it yazmak okuru rahatlatır, çünkü itin kendisi zaten bir yineleyici olduğunu söylüyor. Ama bir işlevin dönüş tipi çağıran tarafın davranışını belirliyorsa — örneğin dönen şey bir referans mı, bir kopya mı — bu bilgiyi autonun arkasına gizlemek okuru yanlış varsayıma iter. Bu dersin gösterdiği gibi, auto ile auto& arasındaki fark derleyici için görünür bir & işaretidir, ama okuyucu için o işareti fark etmek, koddaki kaynağın aynen mi kaldığını yoksa değiştiğini mi kestirebilmesinin tek yoludur. Bu yüzden bir okuma değişkeninde const auto&, bir elemanları değiştiren döngüde auto& yazmak; asıl tipin kendisi anlam taşıdığında da autoyu atlayıp tipi olduğu gibi yazmak, kısayoldan ödün vermeden okunabilirliği korur.

M11/K04 (C++ Temelleri ve Nesne Tasarımı) bir ifadenin hangi nesneyle çalıştığını ölçmüştü; oradaki kopya sınıfı bir hedefi adlandırıyordu — atamanın ya da yapıcının yazdığı yeni nesneyi. Bu kursun aynen sınıfı ise bambaşka bir şeyi adlandırır: işlemin kaynağı, yani zaten var olan, üzerinde çalışılan nesneyi. for (auto x : v) bir kopya kurduğunda ortada tek bir işlem vardır; K04 o kopyanın hedefini sorardı, bu ders işlemin kaynağa ne bıraktığını sorar. Aynı satır, iki farklı soruya iki farklı cevap verir.

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

Bu dersin ölçtüğü şey, kapsayıcı gezinmesinde çıkarılan tipin kaynağa ne bıraktığıdır. Aynı vektörü iki farklı döngüyle geziyoruz; başlıktaki tek fark bir & işareti.

// auto-referans-ile-yazma.cpp — auto& dongusu kaynagi degistiriyor mu
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> v{5, 7};
    auto once = v;

    for (auto& x : v) x *= 2;

    std::printf("auto& sonrasi:");
    for (int deger : v) std::printf(" %d", deger);
    std::printf("\n");
    std::printf("degisti mi: %s\n", v == once ? "hayir" : "evet");
    return 0;
}
auto& sonrasi: 10 14
degisti mi: evet
// auto-kopya-ile-okuma.cpp — auto kopya olusturuyor, kaynak degismiyor
#include <cstdio>
#include <vector>

int main() {
    std::vector<int> v{5, 7};
    auto once = v;
    int toplam = 0;

    for (auto x : v) {
        toplam += x;
        x *= 2;   // yalnizca kopyayi degistiriyor, v'ye ulasmiyor
    }

    std::printf("auto kopya:");
    for (int deger : v) std::printf(" %d", deger);
    std::printf(", toplam %d\n", toplam);
    std::printf("degisti mi: %s\n", v == once ? "hayir" : "evet");
    return 0;
}
auto kopya: 5 7, toplam 12
degisti mi: hayir

KT41. for (auto& x : v) x *= 2; her elemana kaynağın kendisi üzerinden bağlanır; x *= 2 doğrudan vye yazar, döngü sonunda v değişti. for (auto x : v) ise her adımda bir kopya kurar; x *= 2 yalnız o kopyayı değiştirir, v döngü sonunda aynen kalır — toplam kopyalardan hesaplanmış olsa bile kaynağa hiçbir şey yazılmadı. Tip çıkarımının sınıfı burada başlıktaki tek karakterle belirleniyor: derleyici &i görüp referans mı kopya mı kuracağına karar veriyor, okuyucunun bunu satırın geri kalanından çıkarması gerekiyor. Bu dersin sınıfı, auto& biçiminin verdiği sonuçla adlandırılır: değişti.

Özet

  • Bu dersin merkezi ölçümünün sınıfı değişti: for (auto& x : v) x *= 2; kaynağın kendisine yazar, for (auto x : v) yalnız bir kopyayı değiştirir ve kaynağı aynen bırakır.
  • auto bir ifadenin const’unu ve referansını düşürür; auto& referansı düşürmez, const auto& kopya almadan salt okur.
  • auto&& bir iletme referansıdır: başlatıcı sol değerse T&, sağ değerse T&& çıkarır; bu davranış yalnız çıkarım yapılan bağlamlarda görülür.
  • decltype bir ifadenin tipini olduğu gibi verir, referansı düşürmez; decltype(auto) bir işlevin dönüş tipini aynı kuralla çıkarır ve dönüş ifadesinin parantez içine alınması sonucu değere değil referansa çevirebilir.
  • Yapısal bağlamada auto [ad, adet] kaynağı aynen bırakır, auto& [ad, adet] kaynağı değiştirir — fark yine köşeli parantezden önceki tek & işaretidir.
  • Tip adını yazmak, o ad okura kaynağın davranışı hakkında bir şey söylediğinde gerekçelidir; aksi halde auto kısayolu okunabilirliği bozmaz.

Kurs Kapanışı

Bu kurs boyunca on bir farklı işlemi aynı soruyla ölçtük: bir işlem kaynağa ne bırakıyor? Aşağıdaki tablo her dersin ölçtüğü işlemi, kaynağa kalanı ve o dersin sınırlayıcı ölçümünü özetliyor.

Ders Ölçülen işlem Kaynağa kalan Sınırlayıcı ölçüm
Akıllı İşaretçiler unique_ptr taşındı boş std::move tek başına taşımaz; kaynak aynen kalır, taşıyan yapıcı ya da taşıyan atama çağrılmadıkça
Taşıma Semantiği std::string taşındı belirsiz const nesnede std::move kopyalayan yapıcıya düşer, kaynak aynen kalır
Sıfır ve Beş Kuralı Yıkıcılı sınıfta std::move aynen Aynı satır, sınıfın yazılışına göre iki farklı sınıfa düşer: aynen ya da belirsiz
Kopyalanamaz ve Taşınamaz Tipler Kendi taşıyan yapıcısı boş is_move_constructible_v<Kilit> hayır — kopyalayan yapıcı silinmiş, taşıma hiç bildirilmemiş
Kapsayıcılar map’te operator[] ile okuma değişti const map’te operator[] derlenmez; okumak için bile yazma izni ister
Yineleyiciler ve Geçerlilik Silinen yineleyici belirsiz Aynı silme std::list’te yalnız silinen düğümün yineleyicisini geçersizleştirir, ötekiler aynen kalır
Algoritmalar std::sort değişti sort ve stable_sort ikisi de değişti, ama eşit elemanların sırası yalnız birinde garanti
Sil–Kaldır Deyimi std::remove sonrası kuyruk belirsiz C++20’nin serbest std::erase’i kuyruğa hiç dokunmadan doğrudan değiştiye götürür
Lambdalar ve Fonksiyon Nesneleri mutable değerle yakalama aynen Aynı gövde üç farklı yakalamada üç sınıf verir: değer aynen, referans değişti, taşıma belirsiz
Aralıklar Sol değerden görünüm aynen Sağ değerden kurulan görünüm taşıyan yapıcıyla kaynağı boşa çevirir
Tip Çıkarımı auto& ile döngü değişti auto ile auto& arasındaki tek & kaynağı aynenden değiştiye çevirir

3 aynen · 3 değişti · 2 boş · 3 belirsiz; durumu söylenen 8, söylenmeyen 3. Bu dağılım tablodaki on bir dersin dağılımıdır ve kursun başında kurulan çekirdeğin dağılımıyla birebir aynıdır: her işlem kaynağı ya olduğu gibi bırakır, ya değiştirir, ya sözleşmeyle boşaltır, ya da değerini söylemeden bırakır ve ona ancak yeni bir değer verildikten sonra bakılabilir.

Sıradaki kurs Şablonlar ve Başarım; şablonlar, eşzamanlılık ve başarım konularını işliyor.

İ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