İçindekiler
C++’ta const T& gördüğümüzde genellikle “güvenli, değiştirilemez referans” diye düşünürüz. Ancak geçici nesneler devreye girdiğinde konu yalnızca değiştirilebilirlikten ibaret değildir. Referansın neye, nerede ve nasıl bağlandığı; geçici nesnenin hemen yok edilmesini de kapsam sonuna kadar yaşamasını da sağlayabilir. Kısacası const, bazen nesneye adeta fazladan can verir.
``
Geçici nesne nedir?
Bir ifadenin sonucunu taşımak için oluşturulan, genellikle adı olmayan nesneye geçici nesne denir. Örneğin std::string("merhaba") bir geçici nesne üretir. Normal koşullarda bu nesne, içinde oluşturulduğu tam ifadenin sonunda yok edilir. Çoğu zaman bu nokta noktalı virgüldür.
std::string("merhaba"); // Nesne bu satırın sonunda yok edilir.
Basitleştirilmiş biçimde geçici nesnenin olağan ömrünü şöyle düşünebiliriz:
\[L_{geçici} = t_{ifade\ sonu} - t_{oluşturma}\]Fakat uygun bir yerel referansa doğrudan bağlama yapılırsa ömür, referansın kapsamına kadar uzatılabilir:
\[L_{uzatılmış} = t_{kapsam\ sonu} - t_{oluşturma}\]const referans neden özel?
Lvalue referansları normalde geçici nesnelere bağlanamaz:
std::string& a = std::string("C++"); // Hata
Bunun nedeni, kısa ömürlü ve isimsiz bir değerin değiştirilebilir bir lvalue referansı üzerinden kullanılmasının riskli kabul edilmesidir. Buna karşılık const lvalue referansı bağlanabilir:
const std::string& a = std::string("C++");
std::cout << a; // Güvenli: geçicinin ömrü a'nın kapsamına uzatıldı.
| Bağlama biçimi | Geçiciye bağlanır mı? | Ömür uzar mı? |
|---|---|---|
T& |
Hayır | Hayır |
const T& |
Evet | Genellikle evet |
T&& |
Evet | Yerel değişkense evet |
Fonksiyon parametresi const T& |
Evet | Yalnızca çağrının tam ifadesinin sonuna kadar |
Son satır kritik bir ayrıntıdır. Bir fonksiyon parametresine bağlanan geçici, fonksiyon çağrısı bittikten sonra saklanamaz.
const std::string* adres;
void sakla(const std::string& metin) {
adres = &metin;
}
sakla(std::string("kaybolacak"));
// Bu noktadan sonra adres dangling pointer!
metin, fonksiyon içinde geçerlidir; fakat çağrının bulunduğu tam ifade sona erince geçici nesne yok edilir.
Referansı döndürmek ömrü uzatmaz
En tehlikeli sürprizlerden biri, fonksiyondan const referans döndürmektir:
const std::string& üret() {
return std::string("geçici"); // Hatalı: yok edilecek nesneye referans
}
const burada koruyucu kalkan değildir. Fonksiyon sona erdiğinde geçici nesne yok edilir ve dönen referans boşa çıkar. Doğru yaklaşım değeri döndürmektir:
std::string üret() {
return std::string("güvenli");
}
Modern C++, kopya eleme ve taşıma sayesinde bu kodu çoğunlukla ek maliyet oluşturmadan çalıştırır.
İkinci referans ikinci bir ömür vermez
Ömür uzatma aktarılabilir bir özellik değildir:
const std::string& ilk = std::string("örnek");
const std::string& ikinci = ilk;
Nesnenin ömrü ilk nedeniyle uzatılmıştır. ikinci, aynı nesneye başka bir takma ad verir; ömrü yeniden başlatmaz veya kendi kapsamına göre genişletmez. Eğer ikinci, ilkten daha uzun yaşarsa dangling referansa dönüşebilir.
const dönüş değeri her zaman yararlı değildir
Eski kodlarda const T döndüren fonksiyonlar görülebilir:
const std::string oluştur();
Bu kullanım, modern C++’ta taşıma işlemlerini engelleyebilir; çünkü taşıma kurucuları çoğunlukla T&& bekler, const T&& değil. Değer döndüren fonksiyonlarda genellikle yalnızca T tercih edilmelidir.
Özet kural şudur: Yerel const T& ve T&&, doğrudan bağlandıkları geçicinin ömrünü uzatabilir. Parametreler, döndürülen referanslar ve başka bir referans üzerinden yapılan bağlamalar için aynı güvenceyi varsaymayın. const, değişikliği sınırlar; her durumda ömür yönetmez.
Yorumlar