Saya baru saja menghabiskan cukup banyak waktu untuk men-debug masalah korupsi memori yang tidak jelas di salah satu program saya. Ini pada dasarnya bermuara pada fungsi yang mengembalikan struktur dengan nilai yang dipanggil dengan cara yang meneruskannya ke konstruktor objek. Kode pseudo mengikuti.

extern SomeStructure someStructureGenerator();

class ObjectWhichUsesStructure {
  ObjectWhichUsesStructure(const SomeStructure& ref): s(ref) {}
  const SomeStructure& s;
}

ObjectWhichUsesStructure obj(someStructureGenerator());

Alasan saya adalah: someStructureGenerator() mengembalikan sementara; ini terikat pada referensi const, yang berarti kompiler memperpanjang masa pakai sementara agar sesuai dengan tempat penggunaan; Saya menggunakannya untuk membuat objek, jadi masa pakai sementara diperpanjang agar sesuai dengan objek.

Bagian terakhir itu ternyata tidak demikian. Setelah konstruktor keluar, kompiler menghapus sementara dan sekarang obj berisi referensi ke hyperspace, dengan hasil yang lucu ketika saya mencoba menggunakannya. Saya perlu secara eksplisit mengikat referensi const ke ruang lingkup, seperti ini:

const auto& ref = someStructureGenerator();
ObjectWhichUsesStructure obj(ref);

Bukan itu yang saya tanyakan.

Yang saya tanyakan adalah ini: kompiler saya adalah gcc 8, saya membangun dengan -Wall, dan sangat senang mengkompilasi kode di atas --- bersih, tanpa peringatan. Program saya berjalan dengan gembira (tetapi salah) di bawah valgrind, juga tanpa peringatan.

Saya tidak tahu berapa banyak tempat lain dalam kode saya, saya menggunakan idiom yang sama. Alat kompiler apa yang akan mendeteksi dan menandai tempat-tempat ini sehingga saya dapat memperbaiki kode saya, dan mengingatkan saya jika saya membuat kesalahan yang sama di masa mendatang?

2
David Given 5 Maret 2019, 03:22

1 menjawab

Jawaban Terbaik

Pertama, pengikatan referensi melakukan “memperpanjang masa pakai” di sini—tetapi hanya hingga masa pakai parameter konstruktor (yang tidak lebih lama dari parameter sementara yang terwujud). s(ref) tidak mengikat objek (karena ref sudah menjadi referensi), jadi tidak ada ekstensi lebih lanjut yang terjadi.

Oleh karena itu, ekstensi yang Anda harapkan dapat dilakukan melalui inisialisasi agregat:

struct ObjectWhichUsesStructure {
  const SomeStructure &s;
};

ObjectWhichUsesStructure obj{someStructureGenerator()};

Di sini tidak ada parameter konstruktor (karena tidak ada konstruktor sama sekali!) dan hanya satu, pengikatan yang diinginkan terjadi.

Patut diperhatikan mengapa kompiler tidak memperingatkan tentang hal ini: bahkan jika konstruktor mempertahankan referensi ke argumen sementara, ada situasi yang sah yang berfungsi:

void useWrapper(ObjectWhichUsesStructure);
void f() {useWrapper(someStructureGenerator());}

Di sini SomeStructure tinggal sampai akhir pernyataan, selama waktu useWrapper dapat memanfaatkan referensi di ObjectWhichUsesStructure secara menguntungkan.

Dengan mengorbankan melarang kasus penggunaan yang valid di atas, Anda dapat membuat kompiler menjebak kasus bermasalah dengan memberikan konstruktor yang dihapus dengan mengambil referensi nilai:

struct ObjectWhichUsesStructure {
  ObjectWhichUsesStructure(const SomeStructure& ref): s(ref) {}
  ObjectWhichUsesStructure(SomeStructure&&)=delete;
  const SomeStructure& s;
};

Ini mungkin layak dilakukan sementara sebagai tindakan diagnostik tanpa harus menjadi pembatasan permanen.

1
Davis Herring 24 Agustus 2019, 20:42