Apakah boleh mengembalikan nilai argumen default dengan referensi const?

26

Apakah boleh mengembalikan nilai argumen default dengan referensi const seperti pada contoh di bawah ini:

https://coliru.stacked-crooked.com/a/ff76e060a007723b

#include <string>

const std::string& foo(const std::string& s = std::string(""))
{
    return s;
}

int main()
{
    const std::string& s1 = foo();
    std::string s2 = foo();

    const std::string& s3 = foo("s");
    std::string s4 = foo("s");
}
Hati yang beku
sumber
3
Tes sederhana: ganti std::stringdengan kelas Anda sendiri sehingga Anda dapat melacak konstruksi dan penghancuran.
user4581301
1
@ user4581301 Jika urutannya benar itu tidak akan membuktikan bahwa konstruksinya ok.
Peter - Reinstate Monica
6
@ user4581301 "Tampaknya bekerja ketika saya mencobanya" adalah hal terburuk mutlak tentang perilaku yang tidak terdefinisi
HerrJoebob
Perlu dicatat bahwa pertanyaannya adalah berita gembira yang menyesatkan dalam kata-katanya. Anda tidak mengembalikan nilai argumen default dengan referensi const, tetapi Anda mengembalikan referensi const ke referensi const (... ke argumen default).
Damon
2
@ HerrJoebob Setuju dengan pernyataan 100%, tetapi bukan konteks tempat Anda menggunakannya. Cara saya membacanya, pertanyaan ini memutuskan untuk "Kapan masa pakai objek berakhir?" mencari tahu kapan destructor dipanggil adalah cara yang baik untuk melakukan itu. Untuk variabel Otomatis, destruktor harus dipanggil tepat waktu atau Anda punya masalah besar.
user4581301

Jawaban:

18

Dalam kode Anda, keduanya s1dan s3menggantung referensi. s2dan s4baiklah.

Dalam panggilan pertama, std::stringobjek kosong sementara yang dibuat dari argumen default akan dibuat dalam konteks ekspresi yang berisi panggilan. Oleh karena itu, ia akan mati pada akhir definisi s1, yang meninggalkan s1menggantung.

Dalam panggilan kedua, std::stringobjek sementara digunakan untuk menginisialisasi s2, lalu mati.

Dalam panggilan ketiga, string literal "s"digunakan untuk membuat std::stringobjek sementara dan yang juga mati pada akhir definisi s3, meninggalkan s3menggantung.

Dalam panggilan keempat, std::stringobjek sementara dengan nilai "s"digunakan untuk menginisialisasi s4dan kemudian mati.

Lihat C ++ 17 [class.t Sementara] /6.1

Objek sementara yang terikat ke parameter referensi dalam panggilan fungsi (8.2.2) berlanjut hingga penyelesaian ekspresi penuh yang berisi panggilan.

Brian
sumber
1
Bagian yang menarik dari jawabannya adalah pernyataan bahwa argumen default akan dibuat dalam konteks pemanggil. Itu tampaknya didukung oleh kutipan standar Guillaume.
Peter - Pasang kembali Monica
2
@ Peter-ReinstateMonica Lihat [expr.call] / 4, "... Inisialisasi dan penghancuran setiap parameter terjadi dalam konteks fungsi panggilan. ..."
Brian
8

Itu tidak aman :

Secara umum, masa temporer tidak dapat diperpanjang dengan "meneruskannya": referensi kedua, yang diinisialisasi dari referensi dimana temporer terikat, tidak mempengaruhi masa pakainya.

Pelupaan
sumber
Jadi menurut Anda std::string s2 = foo();valid (toh, tidak ada referensi yang diteruskan secara eksplisit)?
Peter - Reinstate Monica
1
@ Peter-ReinstateMonica bahwa seseorang aman karena objek baru akan dibangun. Jawaban saya hanyalah tentang ekspansi seumur hidup. Dua jawaban lainnya sudah mencakup segalanya. Saya tidak akan mengulangi lagi.
Oblivion
5

Itu tergantung pada apa yang Anda lakukan dengan string sesudahnya.

Jika pertanyaan Anda apakah kode saya benar? maka ya itu.

Dari [dcl.fct.default] / 2

[ Contoh : Deklarasi

void point(int = 3, int = 4);

mendeklarasikan fungsi yang bisa dipanggil dengan nol, satu, atau dua argumen bertipe int. Ini dapat dipanggil dengan salah satu dari cara-cara ini:

point(1,2);  point(1);  point();

Dua panggilan terakhir setara dengan point(1,4)dan point(3,4), masing-masing. - contoh akhir ]

Jadi kode Anda secara efektif setara dengan:

const std::string& s1 = foo(std::string(""));
std::string s2 = foo(std::string(""));

Semua kode Anda benar, tetapi tidak ada ekstensi seumur hidup referensi dalam semua kasus ini, karena jenis pengembalian adalah referensi.

Karena Anda memanggil fungsi dengan sementara, masa string yang dikembalikan tidak akan memperpanjang pernyataan.

const std::string& s1 = foo(std::string("")); // okay

s1; // not okay, s1 is dead. s1 is the temporary.

Contoh Anda dengan s2tidak apa-apa karena Anda menyalin (atau memindahkan) dari sementara sebelum akhir satement. s3memiliki masalah yang sama dengan s1.

Guillaume Racicot
sumber