C ++ Yerel değişkene dönen referans


117

Aşağıdaki kod (func1 ()) i döndürmesi gerekiyorsa doğru mu? Yerel bir değişkene referans verirken bir sorun olduğunu bir yerde okuduğumu hatırlıyorum. Func2 () 'den farkı nedir?

int& func1()
{
    int i;
    i = 1;
    return i;
}

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

1
Dinamik olarak ayrılmış belleği kullanmak için func1 () 'i değiştirirseniz, bunlar aynıdır :-)int& i = * new int;
Martin York

Yanıtlar:


193

Bu kod pasajı:

int& func1()
{
    int i;
    i = 1;
    return i;
}

işlev çağrısının kapsamıyla sınırlı bir ömre sahip bir nesneye bir takma ad (başvuru) döndürdüğünüz için çalışmaz. Bu, bir kez func1()döndüğünde, int iölür, işlevden dönen referansı değersiz hale getirir, çünkü artık var olmayan bir nesneye atıfta bulunur.

int main()
{
    int& p = func1();
    /* p is garbage */
}

İkinci sürüm çalışır çünkü değişken, işlev çağrısının yaşam süresine bağlı olmayan ücretsiz depoda tahsis edilir. Ancak, deletetahsisi sizin sorumluluğunuzdadır int.

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

int main()
{
    int* p = func2();
    /* pointee still exists */
    delete p; // get rid of it
}

Tipik olarak, işaretçiyi bir RAII sınıfına ve / veya bir fabrika işlevine kaydırırsınız, böylece kendinize gerek kalmaz delete.

Her iki durumda da, değeri kendisi döndürebilirsiniz (sağladığınız örneğin muhtemelen uydurulmuş olduğunu fark etsem de):

int func3()
{
    return 1;
}

int main()
{
    int v = func3();
    // do whatever you want with the returned value
}

Büyük nesneleri aynı şekilde func3()döndürmenin tamamen iyi olduğunu unutmayın, çünkü günümüzde hemen hemen her derleyici bir tür dönüş değeri optimizasyonu uygulamaktadır :

class big_object 
{ 
public:
    big_object(/* constructor arguments */);
    ~big_object();
    big_object(const big_object& rhs);
    big_object& operator=(const big_object& rhs);
    /* public methods */
private:
    /* data members */
};

big_object func4()
{
    return big_object(/* constructor arguments */);
}

int main()
{
     // no copy is actually made, if your compiler supports RVO
    big_object o = func4();    
}

İlginç bir şekilde, geçici bir sabit referansına bağlamak tamamen yasal C ++ ' dır .

int main()
{
    // This works! The returned temporary will last as long as the reference exists
    const big_object& o = func4();    
    // This does *not* work! It's not legal C++ because reference is not const.
    // big_object& o = func4();  
}

2
Güzel açıklama. : hattip: Üçüncü kod parçacığında, int* p = func2(); delete p;şimdi siliyorsunuz , 'p'yi sildiğinizde, bu işlevin func2()tanımının "içinde" ayrılmış olan belleğin de silinmiş olduğu anlamına mı geliyor?
Aquarius_Girl

2
@Anisha Kaul: Evet. Hafıza içeriye tahsis edildi func2()ve bir sonraki satırda dışarıya bırakıldı. Yine de, bellekle başa çıkmanın hataya açık bir yolu, bunun yerine RAII'nin bazı varyantlarını kullanacağınızı söylediğim gibi. Bu arada, C ++ öğreniyormuşsun gibi konuşuyorsun. Öğrenmek için iyi bir başlangıç ​​C ++ kitabı almanızı öneririm . Ayrıca, bir sorunuz varsa, ileride başvurmak için soruyu her zaman Stack Overflow'da yayınlayabilirsiniz. Yorumlar tamamen yeni sorular sormak için tasarlanmamıştır.
In silico

Şimdi anladım, doğru yaptın! İşlev bir işaretçi döndürüyordu ve bu işlevin dışında işaret ettiği belleği sildiniz. Şimdi açık ve bağlantı için teşekkürler.
Aquarius_Girl

ve yanıtı düzenlediniz mi? : deli: Kolayca kaçırabilirdim. ;);)
Aquarius_Girl

@Anisha Kaul: Hayır, yapmadım. Gönderimin altındaki zaman damgasına göre cevabımı en son 10 Ocak'ta düzenledim.
In silico

18

Yerel bir değişken yığın üzerindeki bellektir, kapsam dışına çıktığınızda bu bellek otomatik olarak geçersiz kılınmaz. Daha derin yuvalanmış bir İşlevden (bellekteki yığında daha yüksek), bu belleğe erişmek tamamen güvenlidir.

İşlev geri dönüp bittiğinde işler tehlikeli hale gelir. Geri döndüğünüzde genellikle bellek silinmez veya üzerine yazılmaz, yani bu adresteki bellek hala verilerinizi içerir - işaretçi geçerli görünür.

Ta ki başka bir işlev yığını oluşturup üzerine yazana kadar. Bu nedenle bir süre işe yarayabilir - ve özellikle derinlemesine iç içe geçmiş bir işlev kümesi veya gerçekten çok büyük boyutlu veya çok sayıda yerel nesneye sahip bir işlev bu yığın belleğe tekrar ulaştığında birdenbire çalışmayı durdurabilir.

Hatta aynı program kısmına tekrar ulaşıp eski yerel fonksiyon değişkeninizin üzerine yeni fonksiyon değişkeni yazmanız bile söz konusu olabilir. Bütün bunlar çok tehlikelidir ve şiddetle tavsiye edilmemelidir. Yerel nesnelere işaretçi kullanmayın!


2

Hatırlanması gereken iyi bir şey, bu basit kurallar ve hem parametreler hem de dönüş türleri için geçerlidir ...

  • Değer - söz konusu öğenin bir kopyasını oluşturur.
  • İşaretçi - söz konusu öğenin adresini ifade eder.
  • Referans - kelimenin tam anlamıyla söz konusu öğedir.

Her birinin bir zamanı ve yeri vardır, bu yüzden onları tanıdığınızdan emin olun. Yerel değişkenler, burada gösterdiğiniz gibi, sadece işlev kapsamında yerel olarak canlı oldukları süre ile sınırlıdır. Örneğinizde bir dönüş türüne sahip olmak int*ve geri dönmek &ieşit derecede yanlış olurdu. Böyle yaparsan daha iyi olur ...

void func1(int& oValue)
{
    oValue = 1;
}

Bunu yapmak, doğrudan geçirdiğiniz parametrenin değerini değiştirir. Oysa bu kod ...

void func1(int oValue)
{
    oValue = 1;
}

olmazdı. Yalnızca oValuelocal değerini işlev çağrısına değiştirir. Bunun nedeni, aslında kendisini oValuedeğil, yalnızca "yerel" bir kopyasını değiştiriyor olmanızdır oValue.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.