Gönderen Konu: auto değişkeni referansla döndürme [cpp]  (Okunma sayısı 11160 defa)

auto değişkeni referansla döndürme [cpp]

« : 29.06.2006 23:54:14 »
Hızlı düğmeleri aç

scg

İleti: 214

Çevrimdışı
  • ***
  • Full Member
    • Profili Görüntüle
    • http://
Forumda yine in cin top atıyo , hele koding bölümü ... Neyse.
Akşam aklıma takılan bişeyi buraya yazıyim dedim.

Auto bi değişkeni referansla döndürme sakat bi iş , yapmayın etmeyin. şöyle bir örnek yazdım ve ne oluyo baktım :

Kod: [Seç]



#include <windows.h>

#include <iostream>

struct hede
{
float a,b;
};


hede &DoHede(const hede& h1 , const hede& h2)
{
hede h; // auto

h.a = h1.a + h2.a;

h.b = h1.b + h2.b;

//hede &z = h;

return h;

}



void main()
{
hede h1,h2;

h1.a = 1.1f; h1.b = 2.2f;
h2.a = 3.3f; h2.b = 4.4f;

// olmayan bişeyi gösteriyon !!!
hede &p = DoHede( h1 , h2);

// kopyalama var , no problem
hede c = DoHede( h1 , h2);

std::cout<<&quot; p.a &quot;<<p.a<<std::endl;
std::cout<<&quot; p.b &quot;<<p.b<<std::endl;

std::cout<<&quot; c.a &quot;<<c.a<<std::endl;
std::cout<<&quot; c.b &quot;<<c.b<<std::endl;



system(&quot;pause&quot;);
return;
}




VC++ 2003 kullanıyorum. Debug modda sonuç beklediğim gibi . p de abidik gubidik şeyler print ediliyo , c olması gerektiği gibi.. Ama Release modda p de yine abidik gubidik şeyler görmeyi beklerken c ile aynı şeyleri görüyorum.

Any ideas??  


Not : Bu foruma cpp syntax highlight özelliği ekleme zor bişey midir ? ..  bide blog cu arkadaşlara soru : blog lara nasıl eklenir cpp syntax highlight?

auto değişkeni referansla döndürme [cpp]

« Yanıtla #1 : 30.06.2006 00:43:49 »
Hızlı düğmeleri aç

skate

İleti: 5.245

A Sinner Scener
Çevrimdışı
  • Administrator
  • *****
  • Hero Member
    • Profili Görüntüle
    • http://www.akaydin.com/
Ben VC++ 2005 ile denedim. Dediğin gibi bir acayiplik var olayda ancak bende Debug modda daha da acayip birşey oldu, p.a doğru sonuç verirken p.b patladı sadece. Release'de sorun gözükmüyor.

Debug result:
 p.a 4.4
 p.b 4.01766e-029
 c.a 4.4
 c.b 6.6

Release result:
 p.a 4.4
 p.b 6.6
 c.a 4.4
 c.b 6.6

şimdi biraz inceleyecem olayı, bir sonuca varırsam buraya yazarım.

auto değişkeni referansla döndürme [cpp]

« Yanıtla #2 : 30.06.2006 01:03:39 »
Hızlı düğmeleri aç

skate

İleti: 5.245

A Sinner Scener
Çevrimdışı
  • Administrator
  • *****
  • Hero Member
    • Profili Görüntüle
    • http://www.akaydin.com/
Sanırım sorunun neden kaynaklandığını buldum ancak hala benim de kafamda soru işaretleri var. Eğer VC++ 2003'de Debug modeda p.a da saçma sapan çıkıyorsa ya 2005 ile farkları var demektir ya da Optimization v.s. ayarlarımız farklıdır. şu anda 2005'de son gördüğüm olay şu ki p.a'yı ekrana yazdırdığımız gibi p'nin içersindeki tüm değerler yamuluyor. Sanki başka yere point etmeye başlıyor gibi. Hatta c'den sonra tekrar p'nin değerlerini yazdırdığımda sonuç:

 p.a 4.4
 p.b 4.01766e-029
 c.a 4.4
 c.b 6.6
 p.a 1.74427e-039
 p.b 4.01766e-029

şeklinde oldu. Ayrıca p.a ve p.b'nin sıralarını değiştirdiğimde ise;

 p.b 6.6
 p.a 1.74427e-039
 c.a 4.4
 c.b 6.6

Sonucu bu şekilde aldım. Kısacası cout komutu bir şekilde olayı bozuyor. Tabii bunun asıl nedeni "CXX0030: Error: expression cannot be evaluated" hatası. Release ederken anladığım kadarıyla yapılan optimizasyonlar sonucunda p'nin değerleri ekrana yazdırmadan önce stable bir alana alınıyor ya da optimizasyonlardan dolayı problem çıkmıyor.

Bu arada aynı testi stdio ve printf kullanarak yaptım, problem aynı ancak verdiği bozuk çıktı değişiyor. Bir de ekrana yazmadan önce p.a++; gibi şeyler denedim, hiçbir şey p'yi bozmuyor, yalnızca output verince bozuluyor.

Benden bu kadar kardeşim, sen de yorumlarını yaz benim bulduğum sonuçlara.

auto değişkeni referansla döndürme [cpp]

« Yanıtla #3 : 30.06.2006 02:00:19 »
Hızlı düğmeleri aç

anesthetic

İleti: 403

Çevrimdışı
  • ****
  • Sr. Member
    • Profili Görüntüle
    • http://resident.tr-demoscene.info/
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:

Kod: [Seç]
#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(&quot;pause&quot;);

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.

auto değişkeni referansla döndürme [cpp]

« Yanıtla #4 : 30.06.2006 02:24:29 »
Hızlı düğmeleri aç

anesthetic

İleti: 403

Çevrimdışı
  • ****
  • Sr. Member
    • Profili Görüntüle
    • http://resident.tr-demoscene.info/
@scg

Alıntı
// kopyalama var , no problem
hede c = DoHede( h1 , h2);

No problem değil işte :). Ölü bir adresin içindeki hede'yi al diyorsun. Eğer o hedenin ölümünden sonra oraya bi şey yazılmamışsa sorun yok (senin örnek gibi), ama yazılmışsa var (benim virgüllü örnek gibi).

@skate

Alıntı
p.a 4.4
p.b 4.01766e-029
c.a 4.4
c.b 6.6
p.a 1.74427e-039
p.b 4.01766e-029

şeklinde oldu. Ayrıca p.a ve p.b'nin sıralarını değiştirdiğimde ise;

p.b 6.6
p.a 1.74427e-039
c.a 4.4
c.b 6.6

p, ölü adresi tutuyor.
c ise ölü adresten bilgi almıştı (adres kirlenmeden).

p.a 4.4 // Ölü adresi kullandık ama henüz kirlenmemişti.
p.b 4.01766e-029 // Oops p.a'yı yazarken yazma fonksiyonu ölü adreste gerçekleşti. Saçma bir opcode okuduk.
c.a 4.4 // Adres kirlenmeden içini c'ye sakladık.
c.b 6.6 // Aynen
p.a 1.74427e-039 // c.b yazılırken ölü adres sapıttı.
p.b 4.01766e-029 // p.a yazılırken ölü adres sapıttı. :)

p.b 6.6 // Ölü adres ama içeriği temiz.
p.a 1.74427e-039 // Adresi p.b'yi yazarken kirlettik.
c.a 4.4 // c adres kirlenmeden kopyalandı.
c.b 6.6

Alıntı
Debug result:
p.a 4.4
p.b 4.01766e-029
c.a 4.4
c.b 6.6

Release result:
p.a 4.4
p.b 6.6
c.a 4.4
c.b 6.6

Release'de optimizasyonlar yapılırken muhtmelen şöyle oldu (ben compiler olsaydım böyle yapardım):
Kullanılan fonksiyonlar (DoHede, operator<<(ostream, float), printf vs...) özyinelemeli (recursive) olmadıklarından, onların datalarının her seferinde allocation deallocation yapmalarını önlemek için özel bir alana ayrıldılar. Yani her recursive olmayan fonksiyon tek bir prototip üzerinde çalışıyor. Herhangi bir anda tek bir thread'in aynı fonksiyonu iki kere çağırmış olması mümkün olmadığından, programın thread başlarken (daha iyi bir ihtimalle ilk function callda) prototip bir alan oluşturup tüm işleri burda halletmesi mümkün. Bu durumda iki fonksiyon ardarda çağrıldığında (DoHede, printf) birbirlerinin alanlarını değişmeli kullanmak yerine kendi alanlarına müdahale edecekleri için birbirlerinin ölü bölgelerini değiştirmeyeceklerdir.

Tabi ki bu doğru bir yargı olmayabilir. Ama yüzde yüz doğru olsa bile buna dayanarak ölü pointer kullanmaya çalışmazdım. Aynı şey referanslar içinde geçerli tabi. Çünkü:

Ölü referans = Gizli ölü pointer :)

auto değişkeni referansla döndürme [cpp]

« Yanıtla #5 : 30.06.2006 09:30:55 »
Hızlı düğmeleri aç

skate

İleti: 5.245

A Sinner Scener
Çevrimdışı
  • Administrator
  • *****
  • Hero Member
    • Profili Görüntüle
    • http://www.akaydin.com/
Detaylı bilgi ve yorumların için sağol Anes, olay biraz daha açıklığa kavuşmuş oldu. Ama senin yukarda yazmış olduğun kuralların bir çoğuna hiç dikkat etmediğim halde SCG'nin yaşadığı gibi bir sorunla hiç karşılaşmadım. Sanırım doğal refleks olarak bu tür kullanımlardan uzak duruyorum ben.

auto değişkeni referansla döndürme [cpp]

« Yanıtla #6 : 30.06.2006 14:11:01 »
Hızlı düğmeleri aç

nightlord

İleti: 1.085

Çevrimdışı
  • Administrator
  • *****
  • Hero Member
    • Profili Görüntüle
    • http://www.nightnetwork.org
aslinda refrenaslar pointerlardan daha okunabilir kod uretir. Cunku pointer dereferanslari (mesela *(*itor)->mHede falan gibi ifadeler) daha zor okunur ve yazarken daha error prone'dur.

fakat mumkun olan her yerde hayvanlar gibi const kullanilirsa(ki kullanilmalidir) anesin bahsettigi yazma cizme tehlikeleri atlatilmis olur.  buna fonksiyonlardan donen parametreler ve classlarin const fonksiyonlari da dahil.

lakin anesin belirttigi referans kullanim durumlarina ilaveten
- iostreamlerde referanslar mesela

std::istream& operator>>(std::istream&, cHede&)

gibi yerlerde referans kullanilmak zorunda ki boylece

oFile >> oHede1 >> oHede2;

gibi seyler calissin.

ve de

- bir managerdan isim/id ile bi objeyi isteyen methodlar:

const cHede& cHedeManager::getHede(std::string oName) const;

tarzi durumlarda da yasasin referanslar diyorum. :)

referanslara pointerlari tercih edecegim iki yer var.
1- eger bir containerda bir class hierarsiden degisik objeler olacaksa, ve container base class tipinde olacaksa... bu durumda referans kullanirsak containera derived class refeanslari atarken slicing olabiliyor. budurumlarda pointerlar (hatta daha iyisi shared_ptr'ler) daha uygun
2- stl algoritmalariyla kullanimda reference to reference problemi diye bilinen durumlar.

std::for_each(oCont.begin(), oCont.end(), std::bind2nd(cHede::func, &oObj));

mesela burda eger cHede::func methodu (tek argumanli bi metod olmak zorunda) referans arguman alamaz. eger referans alan bi fonksiyon olursa template acilimi sirasinda referans to referans durumu dogar (detaylar icin stl headerlari incelenebilir)

auto değişkeni referansla döndürme [cpp]

« Yanıtla #7 : 30.06.2006 19:30:42 »
Hızlı düğmeleri aç

scg

İleti: 214

Çevrimdışı
  • ***
  • Full Member
    • Profili Görüntüle
    • http://
Skate , anes , nightlord buralarda olduğunuzu bilmek güzel :) .
@Skate : Esasında bi sorun değil bu. Sadece merak , beyin cimnastiği gibi bişey işte. şöyle olsa ne oluyo , böyle olsa ne oluyo falan gibi. Zaten böyle bi kod yazdıysan compiler seni uyarır büyük ihtimal. VC++ daki microsoft compiler ı uyarıyo mesela.

Kod: [Seç]
warning C4172: returning address of local variable or temporary


şimdi çoğu kişi için warning ler pek önemli değil de , compiler onu babasının hayrına print etmiyo orda . Yani yüksek warning seviyelerinde çalışmakta ve hatta warning leri error olarak compiler a göstermekte fayda var. VC++ da

Kod: [Seç]
Poject --> Properties --> C/C++ --> General


burada warning seviyesini ayarlayabilip yine warning leri hata olarak kabul ettirebilirsiniz. (Treat warnings as errors /WX )

Alıntı
Reference = Saklı pointer


Hımm ya aslında böyle bi genelleme yapmak ne kadar doğru bilemicem. Cpp Standartında şöyle yazıyo :

 - a reference can be tought of as a name of an object  [bölüm 8.3.2.1]
 
 - it is unspecified whether or not a reference requires storage [bölüm 8.3.2.3]

referansların pointer olarak implement edilip edilmicekleri benim anladığım kadarıyla kompiler yazan adamların insiyatifine bırakılmış. [ Bunu ikinci 8.3.2.3 deki cümleye bakarak söylüyorum , referans storage gerektirmiyosa pointer olamaz]


bide

Alıntı


// kopyalama var , no problem
hede c = DoHede( h1 , h2);


No problem değil işte . :)

.
.
.


Esasında postunun bu kısmındaki açıklama kafama yatmadı. Adım adım açıklamış olmana rağmen :)

Kod: [Seç]
hede c = DoHede( h1 , h2 );

yukardaki satırda dereferencing yok mu?

Kod: [Seç]

int t = 5;

int &r = t;  

int x = r;  // dereferencing ,t x e kopyalandı


Davranış olarak yukarda yazdığım şeyden ne farkı var? Bi de senin f(x) li örneğinle çelişiyo gibi.. hede c = DoHede(h1 , h2); den sonra bir sürü DoHede( hn, tn) çağırsam bile
sonuç c ye kopyalanmış olmucak mı?(DoHede(h1 , h2) den dolayı . hede c var çünkü , bu referans değil , hede tipinde değişken..)

Neyse bu akşam bu kadar kıllık yeter hehe :) Haydin kalın sağlıcakla .

auto değişkeni referansla döndürme [cpp]

« Yanıtla #8 : 01.07.2006 00:12:35 »
Hızlı düğmeleri aç

anesthetic

İleti: 403

Çevrimdışı
  • ****
  • Sr. Member
    • Profili Görüntüle
    • http://resident.tr-demoscene.info/
@anes

Yanlış:
Alıntı
Daha fantastik olarak "int a = f(7), f(4) ;" (virgül sağdan sola okunur):
Gerçek: Eşittir virgülden önceliklidir. "(int a = f(7)), f(4) ;"
Gerçek: Virgül soldan sağa okunur.
Gerçek: Virgülün sağı return değeridir.

Sonuç: Dilin ufak ayrıntılarına dayanarak kod yazmayın. :)

auto değişkeni referansla döndürme [cpp]

« Yanıtla #9 : 01.07.2006 02:39:52 »
Hızlı düğmeleri aç

nightlord

İleti: 1.085

Çevrimdışı
  • Administrator
  • *****
  • Hero Member
    • Profili Görüntüle
    • http://www.nightnetwork.org
scg:

son yazdıgın r ve t ıceren ornek ılk yazdıgın koddan soyle farklı:

x=r yaptıgın sırada r'nin referans ettıgı t objesi hala yaşıyor. yani t nin lifetime ı içinde r'yi güvenle kullanabilirsin.

oysa fonksiyondan çıkarken local variable'lerin lifetime'ı sona eriyor. artık o objeler dil açısından bakıldığında yok. sen artık olmayan bir objeye referans ediyorsun.

bir fonksiyon eğer referans döndürecekse, o fonksiyondan çıkıldığında hala yaşayan bir objenin referansını döndürmesi halinde anlamlı olabilir ancak. böylece metodu cagıran yer donen ref üzerinden işlem yaparken erişilen obje valid olsun.

Mesela o objenin bir attribute'une ref dondurebılır (ki bu da genelde çok süpheli yapılması gereken bi hareket, tercihen ya yapılmamalı ya da const ref döndürmeli) ya da arasında uml'de "association" diye bilinen ilişki olan iki objeden birinin bi methodu diğerinin referansını döndürebilir. bu iki durumda da ref verilen objelrin kendileri metoddan dönüldükten sonra bile valid çünkü.

auto değişkeni referansla döndürme [cpp]

« Yanıtla #10 : 14.07.2006 19:49:06 »
Hızlı düğmeleri aç

anesthetic

İleti: 403

Çevrimdışı
  • ****
  • Sr. Member
    • Profili Görüntüle
    • http://resident.tr-demoscene.info/
bu arada dün referansla alınan argümanların pointer gibi validliğini kontrol etmeye gerek olmadığını fark ettim.  ;)

auto değişkeni referansla döndürme [cpp]

« Yanıtla #11 : 15.07.2006 17:32:50 »
Hızlı düğmeleri aç

nightlord

İleti: 1.085

Çevrimdışı
  • Administrator
  • *****
  • Hero Member
    • Profili Görüntüle
    • http://www.nightnetwork.org
eger aplikasyon multithread degilse (veya gecilen parametrenin yalnizca single thread'den erisilecegi garanti edilebiliyorsa) :)