Bir const referans sınıfı üyesi geçici bir ömrünü uzatır mı?


171

Bunu neden yapar:

#include <string>
#include <iostream>
using namespace std;

class Sandbox
{
public:
    Sandbox(const string& n) : member(n) {}
    const string& member;
};

int main()
{
    Sandbox sandbox(string("four"));
    cout << "The answer is: " << sandbox.member << endl;
    return 0;
}

Çıktı verin:

Cevap:

Onun yerine:

Cevap: dört


39
Ve sadece daha fazla eğlence için, eğer yazmış cout << "The answer is: " << Sandbox(string("four")).member << endl;olsaydınız, o zaman çalışması garanti edilirdi.

7
@RogerPate Nedenini açıklayabilir misiniz?
Paolo M

16
Merak eden biri için, Roger Pate, dize ("dört") geçici olduğu ve bu geçici ifadenin tam ifadenin sonunda yok edildiği için işe yaradığını , bu nedenle örneğinde SandBox::memberokunduğunda geçici dize hala canlıdır .
PcAF

1
Soru şudur: Bu tür sınıfları yazmak tehlikeli olduğundan, bu tür sınıflara geçici geçişler yapılmasına karşı bir derleyici uyarısı var mıdır ya da referansları saklayan yazma sınıflarını yasaklayan bir tasarım kılavuzu var mı (Stroustroup?)? Referanslar yerine işaretçileri saklamak için bir tasarım kılavuzu daha iyi olacaktır.
Grim Fandango

@PcAF: Geçici string("four")ifadenin neden Sandboxyapıcıdan çıktıktan sonra değil, tam ifadenin sonunda yok edildiğini açıklayabilir misiniz ? Potatoswatter'ın cevabı: Bir
Taylor Nichols

Yanıtlar:


166

Sadece yerel const referanslar ömrünü uzatır.

Standart, bu tür davranışları referans beyanların başlatıcıları bölümündeki §8.5.3 / 5, [dcl.init.ref] 'de belirtir. Örneğinizdeki başvuru, kurucunun bağımsız değişkenine bağlıdır nve nesne nkapsam dışına çıktığında geçersiz olur .

Ömür boyu uzantı, işlev bağımsız değişkeni ile geçişli değildir. §12.2 / 5 [sınıf geçici]:

İkinci bağlam, bir başvurunun geçici olana bağlanmasıdır. Başvurunun bağlı olduğu geçici veya geçici olanın bağlı olduğu bir alt nesneye tam nesne olan geçici, aşağıda belirtilenler dışında başvurunun ömrü boyunca devam eder. Bir kurucunun ctor-başlatıcısındaki (§12.6.2 [class.base.init]) bir referans üyeye geçici bir bağ kurucu çıkana kadar devam eder. Bir işlev çağrısında (§5.2.2 [expr.call]) bir başvuru parametresine geçici bir bağ, çağrıyı içeren tam ifadenin tamamlanmasına kadar devam eder.


49
Daha insan dostu bir açıklama için GotW # 88'i de görmelisiniz: otlarutter.com/2008/01/01/…
Nathan Ernst

1
Ben standart "ikinci bağlam bir referans bir değere bağlı olduğunda" derseniz daha açık olacağını düşünüyorum. OP'ın kodunda öyle de denebilir memberbaşlatılıyor çünkü geçici bağlı memberolan nbağlama için araçlar memberaynı nesneye nbağlı ve bu aslında bu durumda geçici bir nesnedir.
MM

2
@MM Bir değer içeren lvalue veya xvalue başlatıcıların bu değeri genişleteceği durumlar vardır. Teklif makalem P0066 , durumun durumunu gözden geçiriyor .
Patates Suyu

1
C ++ 11'den itibaren, Rvalue referansları, bir constkarantinaya ihtiyaç duymadan geçici bir ömrünü uzatır .
17'de

3
@KeNVinFavo evet, ölü bir nesne kullanmak her zaman UB
Potatoswatter

30

Olanları açıklamanın en basit yolu:

Main () içinde bir dize oluşturdunuz ve onu yapıcıya aktardınız. Bu dize örneği yalnızca yapıcı içinde vardı. Yapıcı içinde, üyeyi doğrudan bu örneği göstermesi için atadınız. Kapsam yapıcıdan ayrıldığında, dize örneği yok edildi ve ardından üye artık var olmayan bir dize nesnesini gösterdi. Sandbox.member öğesinin kapsamı dışında bir referansı işaret etmesi, bu harici örnekleri kapsam içinde tutmaz.

Programınızı istediğiniz davranışı gösterecek şekilde düzeltmek istiyorsanız, aşağıdaki değişiklikleri yapın:

int main()
{
    string temp = string("four");    
    Sandbox sandbox(temp);
    cout << sandbox.member << endl;
    return 0;
}

Şimdi temp, yapıcı sonunda değil main () sonunda kapsam dışına çıkacaktır. Ancak, bu kötü bir uygulamadır. Üye değişkeniniz asla örnek dışında var olan bir değişkene başvuru olmamalıdır. Uygulamada, bu değişkenin ne zaman kapsam dışına çıkacağını asla bilemezsiniz.

Ne öneririm Sandbox.member olarak tanımlamak için const string member;Bu geçici parametrenin verilerini üye parametrenin geçici parametrenin kendisi olarak atamak yerine üye değişkenine kopyalar.


Bunu yaparsam: const string & temp = string("four"); Sandbox sandbox(temp); cout << sandbox.member << endl;Hala çalışır mı?
Yves

@Thomas const string &temp = string("four");, özellikle const string temp("four"); kullanmazsanız aynı sonucu verirdecltype(temp)
MM

@MM Çok teşekkürler şimdi bu soruyu tamamen anlıyorum.
Yves

However, this is bad practice.- neden? Hem temp hem de içeren nesne aynı kapsamda otomatik depolama kullanıyorsa,% 100 güvenli değil mi? Ve bunu yapmazsanız, dize kopyalamak için çok büyük ve çok pahalıysa ne yapardınız?
maksimum

2
@max, çünkü sınıf doğru kapsama sahip olmak için geçici olarak geçirileni zorlamıyor. Bu, bir gün bu gereksinimi unutabileceğiniz, geçersiz geçici değeri geçebileceğiniz ve derleyicinin sizi uyarmayacağı anlamına gelir.
Alex Che

5

Teknik olarak konuşursak, bu programın aslında standart çıktıya bir şey çıkarması gerekmez (başlangıçta tamponlanmış bir akıştır).

  • cout << "The answer is: "Biraz yayacaktır "The answer is: "içine tampon stdout.

  • Daha sonra << sandbox.memberbit, tanımsız davranışıoperator << (ostream &, const std::string &) başlatan sarkan referansı sağlayacaktır .

Bu nedenle, hiçbir şeyin gerçekleşmesi garanti edilmez. Program görünüşte iyi çalışabilir veya stdout bile kızarmadan çökebilir - yani "Cevap:" metni ekranda görünmez.


2
UB olduğunda, tüm programın davranışı tanımlanmamıştır - sadece yürütmenin belirli bir noktasında başlamaz. Bu yüzden bunun "The answer is: "hiçbir yerde yazılacağını kesin olarak söyleyemeyiz .
Toby Speight

0

Çünkü Sandbox yapıcısı geri döndüğünde geçici dizginiz kapsam dışına çıktı ve işgal ettiği yığın başka amaçlarla geri alındı.

Genellikle, referansları asla uzun vadede tutmamalısınız. Kaynaklar argümanlar veya yerel değişkenler için iyidir, asla sınıf üyeleri değildir.


7
"Asla" çok güçlü bir kelimedir.
Fred Larson

17
bir nesneye başvurmanız gerekmediği sürece üyeleri asla sınıflandırmayın. Kopyalara değil diğer nesnelere referans tutmanız gereken durumlar vardır, çünkü bu durumlar referanslar işaretçilerden daha net bir çözümdür.
David Rodríguez -

0

yok olan bir şeyden bahsediyorsun. Aşağıdakiler işe yarayacak

#include <string>
#include <iostream>

class Sandbox
{

public:
    const string member = " "; //default to whatever is the requirement
    Sandbox(const string& n) : member(n) {}//a copy is made

};

int main()
{
    Sandbox sandbox(string("four"));
    std::cout << "The answer is: " << sandbox.member << std::endl;
    return 0;
}
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.