Apakah anggota kelas referensi const memperpanjang umur sementara?

171

Mengapa demikian:

#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;
}

Berikan hasil:

Jawabannya adalah:

Dari pada:

Jawabannya adalah: empat

Kyle
sumber
39
Dan hanya untuk lebih bersenang-senang, jika Anda telah menulis cout << "The answer is: " << Sandbox(string("four")).member << endl;, maka dijamin akan berhasil.
7
@RogerPate Bisakah Anda menjelaskan alasannya?
Paolo M
16
Untuk seseorang yang penasaran, misalnya Roger Pate memposting karya karena string ("empat") bersifat sementara dan sementara itu dihancurkan pada akhir ekspresi penuh , jadi dalam contohnya ketika SandBox::memberdibaca, string sementara masih hidup .
PcAF
1
Pertanyaannya adalah: Karena menulis kelas seperti itu berbahaya, adakah peringatan kompiler terhadap pemberian temporer ke kelas semacam itu , atau adakah pedoman desain (di Stroustroup?) Yang melarang kelas menulis yang menyimpan referensi? Pedoman desain untuk menyimpan pointer bukan referensi akan lebih baik.
Grim Fandango
@ Pvc: Bisakah Anda jelaskan mengapa sementara string("four")dihancurkan pada akhir ekspresi penuh, dan tidak setelah Sandboxkonstruktor keluar? Jawaban Potatoswatter mengatakan A sementara terikat pada anggota referensi di ctor-inisialisasi konstruktor (§12.6.2 [class.base.init]) bertahan hingga konstruktor keluar.
Taylor Nichols

Jawaban:

166

Hanya referensi lokal yang const memperpanjang umur.

Standar tersebut menetapkan perilaku tersebut dalam §8.5.3 / 5, [dcl.init.ref], bagian tentang inisialisasi deklarasi referensi. Referensi dalam contoh Anda terikat dengan argumen konstruktor n, dan menjadi tidak valid ketika objek nterikat keluar dari ruang lingkup.

Ekstensi seumur hidup tidak transitif melalui argumen fungsi. §12.2 / 5 [class.t Sementara]:

Konteks kedua adalah ketika referensi terikat untuk sementara. Sementara yang mana referensi terikat atau sementara yang merupakan objek lengkap untuk suatu sub-objek yang sementara terikat terikat untuk seumur hidup referensi kecuali sebagaimana ditentukan di bawah ini. Ikatan sementara untuk anggota referensi dalam inisialisasi ctor konstruktor (§12.6.2 [class.base.init]) tetap ada sampai konstruktor keluar. Batasan sementara untuk parameter referensi dalam pemanggilan fungsi (§5.2.2 [expr.call]) berlanjut hingga selesainya ekspresi penuh yang berisi panggilan.

Potatoswatter
sumber
49
Anda juga harus melihat GotW # 88 untuk penjelasan yang lebih ramah-manusia: herbalutter.com/2008/01/01/…
Nathan Ernst
1
Saya pikir akan lebih jelas jika standar mengatakan "Konteks kedua adalah ketika referensi terikat dengan nilai awal". Dalam kode OP Anda bisa mengatakan bahwa memberitu terikat untuk sementara, karena menginisialisasi memberdengan ncara mengikat memberke objek yang sama nterikat, dan itu sebenarnya adalah objek sementara dalam kasus ini.
MM
2
@ MM Ada kasus di mana lvalue atau xvalue inisialisasi yang mengandung prvalue akan memperpanjang prvalue. Makalah proposal saya P0066 mengulas keadaan.
Potatoswatter
1
Pada C ++ 11, referensi nilai juga memperpanjang umur sementara tanpa memerlukan constquaifier.
GetFree
3
@KeNVinFavo ya, menggunakan benda mati selalu UB
Potatoswatter
30

Inilah cara paling sederhana untuk menjelaskan apa yang terjadi:

Di main () Anda membuat string dan meneruskannya ke konstruktor. Contoh string ini hanya ada dalam konstruktor. Di dalam konstruktor, Anda menugaskan anggota untuk menunjuk langsung ke instance ini. Ketika ketika lingkup meninggalkan konstruktor, instance string dihancurkan, dan anggota kemudian menunjuk ke objek string yang tidak ada lagi. Memiliki Sandbox.member menunjuk ke referensi di luar ruang lingkupnya tidak akan menampung instance eksternal dalam ruang lingkup.

Jika Anda ingin memperbaiki program Anda untuk menampilkan perilaku yang Anda inginkan, buat perubahan berikut:

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

Sekarang temp akan melewati ruang lingkup di ujung main () alih-alih di ujung konstruktor. Namun, ini adalah praktik buruk. Variabel anggota Anda tidak boleh menjadi referensi ke variabel yang ada di luar instance. Dalam praktiknya, Anda tidak pernah tahu kapan variabel itu akan keluar dari ruang lingkup.

Apa yang saya sarankan adalah mendefinisikan Sandbox.member sebagai const string member;Ini akan menyalin data parameter sementara ke dalam variabel anggota alih-alih menetapkan variabel anggota sebagai parameter sementara itu sendiri.

Squirrelsama
sumber
Jika saya melakukan ini: const string & temp = string("four"); Sandbox sandbox(temp); cout << sandbox.member << endl;Apakah masih akan berfungsi?
Yves
@ Thomas const string &temp = string("four");memberikan hasil yang sama dengan const string temp("four"); , kecuali jika Anda menggunakan decltype(temp)secara khusus
MM
@ MM Terima kasih banyak sekarang saya benar-benar mengerti pertanyaan ini.
Yves
However, this is bad practice.- mengapa Jika temp dan objek yang mengandung menggunakan penyimpanan otomatis dalam cakupan yang sama, bukankah 100% aman? Dan jika Anda tidak melakukan itu, apa yang akan Anda lakukan jika string terlalu besar dan terlalu mahal untuk disalin?
maks
2
@ Max, karena kelas tidak memberlakukan lulus secara sementara untuk memiliki ruang lingkup yang benar. Ini berarti bahwa suatu hari Anda mungkin lupa tentang persyaratan ini, memberikan nilai sementara tidak valid dan kompiler tidak akan memperingatkan Anda.
Alex Che
5

Secara teknis, program ini tidak diharuskan untuk benar-benar mengeluarkan apapun ke keluaran standar (yang merupakan aliran buffered untuk memulai).

  • The cout << "The answer is: "bit akan memancarkan "The answer is: "ke penyangga dari stdout.

  • Kemudian << sandbox.memberbit akan memasok referensi yang menggantung ke dalam operator << (ostream &, const std::string &), yang memanggil perilaku tidak terdefinisi .

Karena itu, tidak ada yang dijamin terjadi. Program ini mungkin bekerja dengan baik atau macet tanpa harus menyiram stdout - artinya teks "Jawabannya adalah:" tidak akan muncul di layar Anda.

Tanz87
sumber
2
Ketika ada UB, perilaku seluruh program tidak ditentukan - tidak hanya dimulai pada titik tertentu dalam eksekusi. Jadi kita tidak bisa mengatakan dengan pasti bahwa itu "The answer is: "akan ditulis di mana saja.
Toby Speight
0

Karena string sementara Anda keluar dari ruang lingkup begitu konstruktor Sandbox kembali, dan tumpukan yang ditempati itu direklamasi untuk beberapa tujuan lain.

Secara umum, Anda tidak boleh menyimpan referensi jangka panjang. Referensi baik untuk argumen atau variabel lokal, tidak pernah anggota kelas.

Fyodor Soikin
sumber
7
"Tidak pernah" adalah kata yang sangat kuat.
Fred Larson
17
tidak pernah anggota kelas kecuali Anda perlu menyimpan referensi ke objek. Ada kasus di mana Anda perlu menyimpan referensi ke objek lain, dan bukan salinan, karena kasus-kasus referensi adalah solusi yang lebih jelas daripada pointer.
David Rodríguez - dribeas
0

Anda mengacu pada sesuatu yang telah lenyap. Berikut ini akan berfungsi

#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;
}
pcodex
sumber