Reference = Saklı pointer
Fonksiyondan referans dönerken, aslında gerçekte referansı dönülen objenin adresini dönüyoruz. Yani
bir zamanlar fonksiyon içinde local olarak kullanılmış bir değişkenin
bir zamanlar kullandığı adresi.
Fonksiyondan aldığımız adres aslında ölü adres, garbage bitlerden oluşuyor. Ben o garbage alanı okuduğumda eski kullanılan bilgiyi görmeyi beklerim, eğer o alan sonradan başka bi şey için kullanılmamışsa.
Release için sorun yok. Çünkü gerçekten başka bir allocation işlemi yapılmıyor. ışletim sistemi o alanı büyük ihtimalle başka processe de vermeyecektir.
Ama, bu o alanı dilediğimiz gibi kullanırız anlamına gelmiyor tabi. Ölü adresleri hiç bir zaman kullanmayın.
Debug modda ise stack historysi vs ekstra bilgiler için daha allocation işlemi olacaksa (ve de o alan kullanılacaksa), ya da o alana fonksiyondan ne zaman dönüldüğünü öğrenme amaçlı bir timestamp konulacaksa eski bilgiye tabi ki ulaşamayacağız.
Bunu aynen diskten dosya silmek gibi düşünebiliriz. Dosyayı silmek sadece dosya sisteminden dosyanın bilgisini kaldırmak olacaktır. Fiziksel olarak dosya hala eski sektörlerinde bulunuyor olacaktır. Ama dosya sistemi bu sektörleri boş olarak işaretleyecektir. Yani o sektörlere başka bir şey yazılmadığı sürece silinen o dosyayı geri getirmek mümkündür.
Olayı daha rahat kavramak için şu örneği inceleyelim:
#include <iostream>
#include <cstdlib>
int &f(int x)
{
int a = x;
return a;
}
int main()
{
int &a = f(4);
f(7);
std::cout << a << std::endl;
system("pause");
return 0;
}
Burada f fonksiyonu aldığı değeri lokal bir değişkene yazıyor ve bu değişkenin adresini dönüyor. Aonra biz kendi a değişkenimize f(4)'ü yazıyoruz. Yani
bir zamanlar içinde 4 olan alanın adresini.
Sonra aynı fonksiyonu 7 ile çağırıyoruz. Bu da elimizde tuttuğumuz bu adrese 7 yazmak ve ordaki objeyi serbest bırakıp geri dönmek oluyor. Elimizde tuttuğumuz adreste 7 yazıyor ama ileride başka amaçlarla kullanılıp bit düzenini değiştirebilir.
Debugta belirttiğim gibi 7 çıktısı görmeyebiliriz (ki ben görüyorum) ama release'de büyük ihtimalle 7 göreceğiz.
Eğer adresi "
int &a = f(4) ;" ile değil "
int a = f(4) ;" ile alsaydık. bu şu anlama gelecekti:
-f(4)'ü çalıştır.
-f(4)'ün döndüğü adresi eline al.
-f(4)'ü bitir.
-Elindeki adresteki int'i a'ya al.
Böylece sonradan o adresin içeriğini değiştirsek bile elimizde hala 4 olacaktı, çünkü adresle işimiz bitmişti.
Daha fantastik olarak "
int a = f(7), f(4) ;" (virgül sağdan sola okunur):
-f(4)'ü çalıştır.
-f(4)'ün döndüğü adresi eline al.
-f(4)'ü kapa.
-f(7)'yi çalıştır (adrese 7 yaz!!!)
-f(7)'yi kapa.
-Elindeki adresteki int'i (7'yi) a'ya al.
Ve sonuçta ekrana 7 yazdı.
Yani
referanslar gizli pointerlar olduğu için pointerlarla yapılmaması gereken şeyleri, referanslarla da yapmalıyız. Pointerlarla ne bekliyorsak, referanslarla da onu beklemeliyiz. Ama ben kendime daha sıkı şu kuralları da dayatıyorum.
-Fonksiyondan asla referans dönme (eşitleme operatörleri hariç).
-Bariz bir hız kaybı söz konusu değilse asla pointer dönme.
-Pointer dönülüyorsa silinmeli mi silinmemeli mi açıkça belirt.
-Pointer dönüyorsan daha iyi bir yöntem ara
-Referansları sadece
const& olarak argüman tipi yap.
-Output argümanları her zaman pointer ile al.
-Fonksiyondan asla referans dönme (eşitleme operatörleri hariç)
Eşitleme operatörlerinde referans dönebilmemin sebebi, zaten sadece lvalue'ların yani geçici ya da sabit olmadığı kesin olan hedelerin eşitlenebilmesi. C++ bu kısıtlamayı kendi yapıyor zaten. Yani dönülen referans (eşitlenen şey) kesinlikle içine bir şey yazabileceğimiz geçici olmayan bir değişken olmak zorunda.
Özetlemek gerekirse
referansların gerektiği (bence) sadece iki yer var:
eşittir operatörlerinin (=, =+, =&...) return tipi ve büyük builtin olmayan tiplerin (class, struct...) tamamının fonksiyona kopyalanmasını engellemek için const& olarak argüman tipi.
örn: C &C::operator=(C const &x) ;
eğer f(x) gibi bi şey görüyorsam f'in x'in değerini değiştirmemesini beklerim. Bu da ya x'in tamamının gönderilmesiyle ya da const&'ının gönderilmesiyle mümkün. Eğer argümanını değiştiren bir fonksiyon varsa onu f(&x) şeklinde görmeyi tercih ederim, ki output argümanı kullandığı belli olsun. Bu yukarıdaki bahsettiğim iki durum haricinde pointerları kullanmak bence daha mantıklı çünkü daha okunabilir bir kod sunuyor.