Pindahkan semantik di C ++ - Pindahkan-kembali variabel lokal

10

Pemahaman saya adalah bahwa dalam C ++ 11, ketika Anda mengembalikan variabel lokal dari suatu fungsi dengan nilai, kompiler diperbolehkan untuk memperlakukan variabel itu sebagai r-nilai referensi dan 'memindahkannya keluar dari fungsi untuk mengembalikannya (jika RVO / NRVO tidak terjadi sebagai gantinya, tentu saja).

Pertanyaan saya adalah, tidak bisakah ini memecahkan kode yang ada?

Pertimbangkan kode berikut:

#include <iostream>
#include <string>

struct bar
{
  bar(const std::string& str) : _str(str) {}
  bar(const bar&) = delete;
  bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
  void print() {std::cout << _str << std::endl;}

  std::string _str;
};

struct foo
{
  foo(bar& b) : _b(b) {}
  ~foo() {_b.print();}

  bar& _b;
};

bar foobar()
{
  bar b("Hello, World!");
  foo f(b);

  return std::move(b);
}

int main()
{
  foobar();
  return EXIT_SUCCESS;
}

Pikiranku adalah bahwa akan mungkin bagi penghancur objek lokal untuk referensi objek yang secara implisit dipindahkan, dan karena itu secara tak terduga melihat objek 'kosong'. Saya mencoba menguji ini (lihat http://ideone.com/ZURoeT ), tetapi saya mendapatkan hasil 'benar' tanpa eksplisit std::movedalam foobar(). Saya menduga itu karena NRVO, tetapi saya tidak mencoba mengatur ulang kode untuk menonaktifkannya.

Apakah saya benar karena transformasi ini (menyebabkan perpindahan fungsi) terjadi secara implisit dan dapat memecah kode yang ada?

PEMBARUAN Berikut adalah contoh yang menggambarkan apa yang saya bicarakan. Dua tautan berikut adalah untuk kode yang sama. http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11

Jika Anda melihat outputnya, itu berbeda.

Jadi, saya kira pertanyaan ini sekarang menjadi, apakah ini dipertimbangkan ketika menambahkan langkah implisit ke standar, dan diputuskan bahwa tidak apa-apa untuk menambahkan perubahan ini karena kode semacam ini cukup langka? Saya juga bertanya-tanya apakah ada kompiler akan memperingatkan dalam kasus-kasus seperti ini ...

Bwmat
sumber
Suatu langkah harus selalu meninggalkan objek dalam keadaan yang dapat dirusak.
Zan Lynx
Ya, tapi bukan itu pertanyaannya. Kode pre-c ++ 11 dapat mengasumsikan bahwa nilai variabel lokal tidak akan berubah hanya karena mengembalikannya, sehingga langkah implisit ini dapat mematahkan asumsi tersebut.
Bwmat
Itulah yang saya coba jelaskan dalam contoh saya; via destructors Anda dapat memeriksa keadaan (subset dari) variabel lokal fungsi 'setelah' pernyataan kembali dieksekusi, tetapi sebelum fungsi benar-benar kembali.
Bwmat
Ini adalah pertanyaan yang sangat bagus dengan contoh yang Anda tambahkan. Saya harap ini mendapat lebih banyak jawaban dari para profesional yang dapat menjelaskan ini. Satu-satunya umpan balik nyata yang dapat saya berikan adalah: inilah mengapa objek biasanya tidak memiliki pandangan yang tidak memiliki data. Sebenarnya ada banyak cara untuk menulis kode tampak tidak bersalah yang segfault ketika Anda memberikan objek pandangan tidak memiliki (raw pointer atau referensi). Saya bisa menguraikan ini dalam jawaban yang tepat jika Anda mau, tapi saya kira itu bukan apa yang ingin Anda dengar. Dan btw, sudah diketahui bahwa 11 dapat memecahkan kode yang ada, misalnya dengan mendefinisikan kata kunci baru.
Nir Friedman
Ya, saya tahu bahwa C ++ 11 tidak pernah mengklaim tidak memecahkan kode lama, tapi ini cukup halus, dan akan sangat mudah untuk dilewatkan (tidak ada kesalahan kompiler, peringatan, segfault)
Bwmat

Jawaban:

8

Scott Meyers memposting ke comp.lang.c ++ (Agustus 2010) tentang masalah di mana generasi konstruktor pemindahan implisit dapat memecahkan invarian kelas C ++ 03:

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

Di sini masalahnya adalah bahwa dalam C ++ 03, Xmemiliki invarian yang vanggotanya selalu memiliki 5 elemen. X::~X()mengandalkan invarian itu, tetapi konstruktor bergerak yang baru diperkenalkan pindah dari v, sehingga menetapkan panjangnya ke nol.

Ini terkait dengan contoh Anda karena invarian rusak hanya terdeteksi di Xdestruktor (seperti yang Anda katakan itu mungkin untuk destruktor objek lokal untuk referensi objek yang dipindahkan secara implisit, dan karena itu secara tak terduga melihat objek kosong ).

C ++ 11 mencoba mencapai keseimbangan antara memecah beberapa kode yang ada dan memberikan optimisasi bermanfaat berdasarkan move constructor.

Komite awalnya memutuskan bahwa memindahkan konstruktor dan memindahkan operator penugasan harus dihasilkan oleh kompiler ketika tidak disediakan oleh pengguna.

Kemudian memutuskan bahwa ini memang memprihatinkan dan membatasi generasi otomatis konstruktor pemindahan dan memindahkan operator penugasan sedemikian rupa sehingga jauh lebih kecil kemungkinannya, walaupun bukan tidak mungkin, untuk memecahkan kode (misalnya destruktor yang didefinisikan secara eksplisit).

Sangat menggoda untuk berpikir bahwa mencegah pembuatan konstruktor pemindahan implisit ketika ada pemecah yang ditentukan pengguna sudah cukup tetapi itu tidak benar ( N3153 - Pemindahan Tersirat Harus Pergi untuk perincian lebih lanjut).

Di N3174 - Untuk Memindahkan atau tidak Memindahkan Stroupstrup mengatakan:

Saya menganggap ini masalah desain bahasa, bukan masalah kompatibilitas ke belakang yang sederhana. Sangat mudah untuk menghindari melanggar kode lama (misalnya hanya menghapus operasi pindahkan dari C ++ 0x), tapi saya melihat membuat C ++ 0x bahasa yang lebih baik dengan membuat operasi pindahkan meresapi tujuan utama yang mungkin bernilai melanggar beberapa C + +98 kode.

manlio
sumber