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. autobir 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ğerseT&, sağ değerseT&&çıkarır; bu davranış yalnız çıkarım yapılan bağlamlarda görülür.decltypebir 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
autokı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.