unique_ptr<T>
tidak mengizinkan pembuatan salinan, melainkan mendukung pemindahan semantik. Namun, saya dapat mengembalikan unique_ptr<T>
dari fungsi dan menetapkan nilai yang dikembalikan ke variabel.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
Kode di atas mengkompilasi dan berfungsi sebagaimana dimaksud. Jadi bagaimana garis 1
itu tidak memanggil copy constructor dan menghasilkan kesalahan kompilator? Jika saya harus menggunakan jalur, 2
itu lebih masuk akal (menggunakan jalur juga 2
berfungsi, tapi kami tidak diharuskan melakukannya).
Saya tahu C ++ 0x memungkinkan pengecualian ini unique_ptr
karena nilai kembali adalah objek sementara yang akan dihancurkan segera setelah fungsi keluar, sehingga menjamin keunikan pointer yang dikembalikan. Saya ingin tahu tentang bagaimana ini diterapkan, apakah itu khusus dimasukkan dalam kompiler atau ada beberapa klausa lain dalam spesifikasi bahasa yang dieksploitasi ini?
sumber
unique_ptr
. Seluruh pertanyaan adalah tentang 1 dan 2 menjadi dua cara berbeda untuk mencapai hal yang sama.main
fungsi keluar, tetapi tidak ketikafoo
keluar.Jawaban:
Ya, lihat 12.8 §34 dan §35:
Hanya ingin menambahkan satu poin lagi bahwa pengembalian dengan nilai harus menjadi pilihan default di sini karena nilai yang disebutkan dalam pernyataan pengembalian dalam kasus terburuk, yaitu tanpa elisi dalam C ++ 11, C ++ 14 dan C ++ 17 diperlakukan sebagai nilai. Jadi misalnya fungsi berikut mengkompilasi dengan
-fno-elide-constructors
flagDengan flag yang diatur pada kompilasi ada dua gerakan (1 dan 2) yang terjadi di fungsi ini dan kemudian satu gerakan di kemudian hari (3).
sumber
foo()
memang juga akan dihancurkan (jika tidak ditugaskan untuk apa pun), seperti nilai kembali dalam fungsi, dan karenanya masuk akal bahwa C ++ menggunakan konstruktor bergerak ketika melakukanunique_ptr<int> p = foo();
?std::unique_ptr
), ada aturan khusus untuk pertama-tama memperlakukan objek sebagai nilai. Saya pikir ini sepenuhnya setuju dengan apa yang dijawab Nikola.Ini sama sekali tidak spesifik untuk
std::unique_ptr
, tetapi berlaku untuk kelas apa saja yang dapat dipindahkan. Ini dijamin oleh aturan bahasa karena Anda kembali berdasarkan nilai. Kompiler mencoba menghilangkan salinan, memanggil pemindah konstruktor jika tidak dapat menghapus salinan, memanggil konstruktor salinan jika tidak dapat bergerak, dan gagal mengkompilasi jika tidak dapat menyalin.Jika Anda memiliki fungsi yang menerima
std::unique_ptr
sebagai argumen, Anda tidak akan dapat meneruskannya. Anda harus secara eksplisit memanggil move constructor, tetapi dalam hal ini Anda tidak boleh menggunakan variabel p setelah panggilan kebar()
.sumber
p
bukan sementara, hasil darifoo()
, apa yang dikembalikan, adalah; jadi itu adalah nilai dan bisa dipindahkan, yang memungkinkan penugasan menjadimain
mungkin. Saya akan mengatakan Anda salah kecuali bahwa Nikola kemudian tampaknya menerapkan aturan ini untukp
dirinya sendiri yang salah.1
dan Garis2
? Dalam pandangan saya itu sama sejak ketika membangunp
dimain
, hanya peduli tentang jenis jenis kembalinyafoo
, kan?unique_ptr tidak memiliki konstruktor salinan tradisional. Alih-alih ia memiliki "pindahkan konstruktor" yang menggunakan referensi nilai:
Referensi nilai (ampers ganda) hanya akan mengikat nilai. Itu sebabnya Anda mendapatkan kesalahan saat Anda mencoba meneruskan lvalue unique_ptr ke suatu fungsi. Di sisi lain, nilai yang dikembalikan dari fungsi diperlakukan sebagai nilai, sehingga konstruktor pemindahan dipanggil secara otomatis.
Omong-omong, ini akan bekerja dengan benar:
Unique_ptr sementara di sini adalah nilai.
sumber
p
- "jelas" sebuah lvalue - diperlakukan sebagai nilai p dalam pernyataan kembalireturn p;
dalam definisifoo
. Saya tidak berpikir ada masalah dengan fakta bahwa nilai balik fungsi itu sendiri dapat "dipindahkan".Saya pikir itu dijelaskan dengan sempurna dalam item 25 dari Scott Meyers ' Effective Modern C ++ . Berikut ini kutipannya:
Di sini, RVO mengacu pada optimalisasi nilai pengembalian , dan jika kondisi untuk RVO terpenuhi berarti mengembalikan objek lokal yang dideklarasikan di dalam fungsi yang Anda harapkan untuk melakukan RVO , yang juga dijelaskan dengan baik dalam item 25 bukunya dengan merujuk pada standar (di sini objek lokal termasuk objek sementara yang dibuat oleh pernyataan kembali). Pengambilan terbesar dari kutipan adalah apakah copy elision terjadi atau
std::move
secara implisit diterapkan pada objek lokal yang dikembalikan . Scott menyebutkan dalam item 25 yangstd::move
diterapkan secara implisit ketika kompiler memilih untuk tidak menghapus salinan dan programmer tidak harus secara eksplisit melakukannya.Dalam kasus Anda, kode ini jelas merupakan kandidat untuk RVO karena mengembalikan objek lokal
p
dan jenisnyap
sama dengan jenis pengembalian, yang menghasilkan salinan salinan. Dan jika kompiler memilih untuk tidak menghapus salinan, untuk alasan apa pun,std::move
akan menendang ke baris1
.sumber
Satu hal yang tidak saya lihat di jawaban lain adalahUntuk mengklarifikasi jawaban lain bahwa ada perbedaan antara mengembalikan std :: unique_ptr yang telah dibuat di dalam suatu fungsi, dan yang telah diberikan ke fungsi itu.Contohnya bisa seperti ini:
sumber