Saya baru saja memindahkan semantik dalam C ++ 11 dan saya tidak tahu bagaimana menangani unique_ptr
parameter dalam konstruktor atau fungsi. Pertimbangkan referensi kelas ini sendiri:
#include <memory>
class Base
{
public:
typedef unique_ptr<Base> UPtr;
Base(){}
Base(Base::UPtr n):next(std::move(n)){}
virtual ~Base(){}
void setNext(Base::UPtr n)
{
next = std::move(n);
}
protected :
Base::UPtr next;
};
Apakah ini cara saya menulis fungsi yang mengambil unique_ptr
argumen?
Dan apakah saya perlu menggunakan std::move
kode panggilan?
Base::UPtr b1;
Base::UPtr b2(new Base());
b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?
c++
arguments
c++11
unique-ptr
codablank1
sumber
sumber
Jawaban:
Berikut adalah cara yang mungkin untuk mengambil pointer unik sebagai argumen, serta artinya yang terkait.
(A) Berdasarkan Nilai
Agar pengguna dapat memanggil ini, mereka harus melakukan salah satu dari yang berikut:
Untuk mengambil pointer unik dengan nilai berarti bahwa Anda mentransfer kepemilikan pointer ke fungsi / objek / dll yang dimaksud. Setelah
newBase
dibangun,nextBase
dijamin kosong . Anda tidak memiliki objek, dan Anda bahkan tidak memiliki pointer ke sana lagi. Itu hilang.Ini dipastikan karena kita mengambil parameter berdasarkan nilai.
std::move
tidak benar-benar memindahkan apa pun; itu hanya pemain mewah.std::move(nextBase)
mengembalikan sebuahBase&&
yang merupakan referensi nilai r kenextBase
. Hanya itu yang dilakukannya.Karena
Base::Base(std::unique_ptr<Base> n)
mengambil argumennya berdasarkan nilai daripada referensi r-nilai, C ++ akan secara otomatis membuat sementara untuk kita. Ini menciptakanstd::unique_ptr<Base>
dariBase&&
yang kami berikan fungsi melaluistd::move(nextBase)
. Ini adalah konstruksi sementara ini yang benar-benar memindahkan nilai darinextBase
ke argumen fungsin
.(B) Dengan referensi nilai-non-const
Ini harus dipanggil pada nilai l aktual (variabel bernama). Itu tidak bisa dipanggil dengan sementara seperti ini:
Arti ini sama dengan arti dari penggunaan lain dari referensi non-const: fungsi mungkin atau mungkin tidak mengklaim kepemilikan pointer. Diberikan kode ini:
Tidak ada jaminan yang
nextBase
kosong. Ini mungkin kosong; mungkin tidak. Itu sangat tergantung pada apa yangBase::Base(std::unique_ptr<Base> &n)
ingin dilakukan. Karena itu, tidak begitu jelas hanya dari tanda tangan fungsi apa yang akan terjadi; Anda harus membaca implementasinya (atau dokumentasi terkait).Karena itu, saya tidak akan menyarankan ini sebagai antarmuka.
(C) Dengan referensi nilai konstan
Saya tidak menunjukkan implementasi, karena Anda tidak dapat beralih dari a
const&
. Dengan melewati aconst&
, Anda mengatakan bahwa fungsi tersebut dapat mengaksesBase
melalui pointer, tetapi tidak dapat menyimpannya di mana saja. Ia tidak dapat mengklaim kepemilikannya.Ini bisa bermanfaat. Tidak harus untuk kasus spesifik Anda, tetapi selalu baik untuk bisa memberikan pointer kepada seseorang dan tahu bahwa mereka tidak bisa (tanpa melanggar aturan C ++, seperti tidak membuang
const
) mengklaim kepemilikan itu. Mereka tidak bisa menyimpannya. Mereka dapat memberikannya kepada orang lain, tetapi yang lain harus mematuhi aturan yang sama.(D) Dengan referensi r-value
Ini kurang lebih identik dengan kasus "oleh referensi non-konstanta-nilai". Perbedaannya adalah dua hal.
Anda dapat lulus sementara:
Anda harus menggunakan
std::move
ketika melewati argumen non-sementara.Yang terakhir ini benar-benar masalah. Jika Anda melihat baris ini:
Anda memiliki harapan yang masuk akal bahwa, setelah baris ini selesai,
nextBase
harus kosong. Seharusnya sudah dipindahkan dari. Setelah semua, Anda memiliki yangstd::move
duduk di sana, memberi tahu Anda bahwa gerakan telah terjadi.Masalahnya adalah belum. Itu tidak dijamin telah dipindahkan dari. Ini mungkin telah dipindahkan dari, tetapi Anda hanya akan tahu dengan melihat kode sumber. Anda tidak bisa membedakan hanya dari tanda tangan fungsi.
Rekomendasi
unique_ptr
, ambil menurut nilainya.unique_ptr
untuk durasi eksekusi fungsi itu, bawa sajaconst&
. Atau, lewati a&
atauconst&
ke tipe aktual yang ditunjuk, daripada menggunakan aunique_ptr
.&&
. Tetapi saya sangat menyarankan untuk tidak melakukan hal ini sedapat mungkin.Bagaimana cara memanipulasi unique_ptr
Anda tidak dapat menyalin
unique_ptr
. Anda hanya bisa memindahkannya. Cara yang tepat untuk melakukan ini adalah denganstd::move
fungsi perpustakaan standar.Jika Anda mengambil
unique_ptr
berdasarkan nilai, Anda dapat berpindah darinya dengan bebas. Tetapi gerakan sebenarnya tidak terjadi karenastd::move
. Ambil pernyataan berikut:Ini benar-benar dua pernyataan:
(catatan: Kode di atas tidak secara teknis mengkompilasi, karena referensi r-nilai non-sementara sebenarnya tidak r-nilai. Ini di sini hanya untuk keperluan demo).
Ini
temporary
hanya referensi r-nilaioldPtr
. Itu adalah di konstruktor dinewPtr
mana gerakan terjadi.unique_ptr
Move's constructor (sebuah constructor yang mengambil a&&
untuk dirinya sendiri) adalah apa gerakan yang sebenarnya.Jika Anda memiliki
unique_ptr
nilai dan Anda ingin menyimpannya di suatu tempat, Anda harus menggunakannyastd::move
untuk melakukan penyimpanan.sumber
std::move
tidak menyebutkan nilai pengembaliannya. Ingat bahwa referensi nilai yang dinamai adalah nilai. ideone.com/VlEM3unique_ptr
; mungkin beberapa penelepon lain memerlukan fungsi yang sama tetapi menahan panggilanshared_ptr
] (2) dengan referensi nilai dapat berguna jika fungsi yang dipanggil memodifikasi pointer, misalnya, menambah atau menghapus node (daftar yang dimiliki) dari daftar yang ditautkan.unique_ptr
nilai dengan rvalue referensi (misalnya ketika mentransformasikannya menjadishared_ptr
). Alasan untuk itu mungkin sedikit lebih efisien (tidak ada pindah ke pointer sementara dilakukan) sementara itu memberikan hak yang sama persis kepada penelepon (dapat melewati nilai, atau nilai yang dibungkusstd::move
, tetapi bukan nilai telanjang).Biarkan saya mencoba untuk menyatakan berbagai mode yang layak melewati pointer ke objek yang ingatannya dikelola oleh sebuah instance dari
std::unique_ptr
templat kelas; ini juga berlaku untukstd::auto_ptr
templat kelas yang lebih lama (yang saya percaya mengizinkan semua penggunaan yang dilakukan oleh pointer unik, tetapi sebagai tambahan nilai yang dapat dimodifikasi akan diterima di mana nilai diharapkan, tanpa harus memintastd::move
), dan sampai batas tertentu jugastd::shared_ptr
.Sebagai contoh nyata untuk diskusi saya akan mempertimbangkan jenis daftar sederhana berikut
Mesin virtual dari daftar tersebut (yang tidak dapat diizinkan untuk berbagi bagian dengan mesin virtual lain atau bundar) sepenuhnya dimiliki oleh siapa pun yang memegang
list
penunjuk awal . Jika kode klien tahu bahwa daftar yang disimpan tidak akan pernah kosong, itu juga dapat memilih untuk menyimpan yang pertamanode
secara langsung daripada alist
. Tidak ada destruktor yangnode
perlu didefinisikan: karena destruktor untuk bidangnya secara otomatis dipanggil, seluruh daftar akan dihapus secara rekursif oleh destruktor penunjuk pintar setelah masa pakai penunjuk atau simpul awal berakhir.Jenis rekursif ini memberikan kesempatan untuk membahas beberapa kasus yang kurang terlihat dalam kasus penunjuk pintar ke data biasa. Juga fungsi-fungsi itu sendiri kadang-kadang memberikan (secara rekursif) contoh kode klien juga. Typedef untuk
list
ini tentu saja condong ke arahunique_ptr
, tetapi definisi dapat diubah untuk menggunakanauto_ptr
ataushared_ptr
bukannya tanpa perlu banyak mengubah apa yang dikatakan di bawah ini (terutama mengenai keamanan pengecualian yang dijamin tanpa perlu menulis destruktor).Mode lewat pointer pintar di sekitar
Mode 0: melewati pointer atau argumen referensi alih-alih smart pointer
Jika fungsi Anda tidak terkait dengan kepemilikan, ini adalah metode yang disukai: jangan membuatnya mengambil pointer cerdas sama sekali. Dalam hal ini fungsi Anda tidak perlu khawatir siapa yang memiliki objek yang ditunjuk, atau dengan cara apa kepemilikan dikelola, sehingga melewati pointer mentah sama-sama aman, dan bentuk yang paling fleksibel, karena terlepas dari kepemilikan, klien selalu dapat menghasilkan pointer mentah (baik dengan memanggil
get
metode atau dari alamat-operator&
).Misalnya fungsi untuk menghitung panjang daftar tersebut, tidak boleh memberikan
list
argumen, tetapi pointer mentah:Klien yang memegang variabel
list head
dapat memanggil fungsi ini sebagailength(head.get())
, sementara klien yang telah memilih untuk menyimpan yangnode n
mewakili daftar yang tidak kosong dapat memanggillength(&n)
.Jika pointer dijamin bukan nol (yang tidak terjadi di sini karena daftar mungkin kosong) orang mungkin lebih suka untuk melewatkan referensi daripada pointer. Ini bisa menjadi pointer / referensi ke non-
const
jika fungsi perlu memperbarui konten node, tanpa menambahkan atau menghapus salah satu dari mereka (yang terakhir akan melibatkan kepemilikan).Kasus menarik yang termasuk dalam kategori mode 0 adalah membuat salinan (dalam) daftar; sementara fungsi melakukan hal ini tentu saja harus mentransfer kepemilikan salinan yang dibuatnya, itu tidak berkaitan dengan kepemilikan daftar yang disalin. Jadi bisa didefinisikan sebagai berikut:
Kode ini layak dicermati, baik untuk pertanyaan mengapa mengkompilasi sama sekali (hasil panggilan rekursif ke
copy
dalam daftar penginisialisasi mengikat argumen referensi nilai dalam konstruktor bergerakunique_ptr<node>
, aliaslist
, ketika menginisialisasinext
bidang dari dihasilkannode
), dan untuk pertanyaan mengapa itu adalah pengecualian-aman (jika selama proses alokasi rekursif memori habis dan beberapa panggilannew
melemparstd::bad_alloc
, maka pada saat itu penunjuk ke daftar yang dikonstruksi sebagian ditahan secara anonim dalam jenis sementaralist
dibuat untuk daftar penginisialisasi, dan destruktornya akan membersihkan daftar sebagian itu). By the way kita harus menahan godaan untuk menggantikan (sebagai awalnya saya lakukan) keduanullptr
olehp
, yang setelah semua diketahui nol pada saat itu: seseorang tidak dapat membangun pointer pintar dari pointer (mentah) ke konstan , bahkan ketika itu diketahui nol.Mode 1: melewati pointer cerdas berdasarkan nilai
Fungsi yang mengambil nilai penunjuk cerdas sebagai argumen memiliki objek yang ditunjuk langsung: penunjuk cerdas yang dipegang pemanggil (baik dalam variabel bernama atau sementara anonim) disalin ke dalam nilai argumen di pintu masuk fungsi dan pemanggil pointer menjadi nol (dalam kasus sementara salinan mungkin telah dielakkan, tetapi dalam hal apa pun penelepon telah kehilangan akses ke objek yang diarahkan ke objek). Saya ingin memanggil panggilan mode ini dengan uang tunai : penelepon membayar di muka untuk layanan yang dipanggil, dan tidak dapat memiliki ilusi tentang kepemilikan setelah panggilan. Untuk memperjelas ini, aturan bahasa mengharuskan penelepon untuk memasukkan argumen
std::move
jika smart pointer disimpan dalam variabel (secara teknis, jika argumennya adalah nilai); dalam kasus ini (tetapi tidak untuk mode 3 di bawah) fungsi ini melakukan apa yang disarankan namanya, yaitu memindahkan nilai dari variabel ke sementara, meninggalkan variabel nol.Untuk kasus-kasus di mana fungsi yang dipanggil tanpa syarat mengambil kepemilikan (pilfers) objek yang ditunjuk, mode ini digunakan dengan
std::unique_ptr
ataustd::auto_ptr
merupakan cara yang baik untuk melewatkan sebuah pointer bersama dengan kepemilikannya, yang menghindari risiko kebocoran memori. Meskipun demikian saya berpikir bahwa hanya ada beberapa situasi di mana mode 3 di bawah ini tidak disukai (sedikit pun) daripada mode 1. Untuk alasan ini saya tidak akan memberikan contoh penggunaan mode ini. (Tapi lihatreversed
contoh mode 3 di bawah ini, di mana dikatakan bahwa mode 1 akan melakukan setidaknya juga.) Jika fungsi tersebut membutuhkan lebih banyak argumen daripada hanya pointer ini, mungkin terjadi bahwa ada tambahan alasan teknis untuk menghindari mode 1 (denganstd::unique_ptr
ataustd::auto_ptr
): karena operasi pemindahan yang sebenarnya terjadi saat melewati variabel penunjukp
oleh ungkapanstd::move(p)
, tidak dapat diasumsikanp
memiliki nilai yang berguna saat mengevaluasi argumen lain (urutan evaluasi tidak ditentukan), yang dapat menyebabkan kesalahan halus; sebaliknya, menggunakan mode 3 memastikan bahwa tidak ada pemindahan darip
tempat terjadi sebelum pemanggilan fungsi, sehingga argumen lain dapat dengan aman mengakses suatu nilaip
.Ketika digunakan dengan
std::shared_ptr
, mode ini menarik karena dengan definisi fungsi tunggal memungkinkan pemanggil untuk memilih apakah akan menyimpan salinan berbagi pointer untuk dirinya sendiri sambil membuat salinan berbagi baru untuk digunakan oleh fungsi (ini terjadi ketika nilai lebih argumen disediakan; copy constructor untuk pointer bersama yang digunakan pada panggilan meningkatkan jumlah referensi), atau hanya memberikan fungsi salinan dari pointer tanpa mempertahankan satu atau menyentuh jumlah referensi (ini terjadi ketika argumen nilai diberikan, mungkin nilai yang dibungkus dengan panggilanstd::move
). ContohnyaHal yang sama dapat dicapai dengan mendefinisikan secara terpisah
void f(const std::shared_ptr<X>& x)
(untuk kasus lvalue) danvoid f(std::shared_ptr<X>&& x)
(untuk kasus rvalue), dengan badan fungsi berbeda hanya dalam versi pertama memanggil semantik salinan (menggunakan konstruksi copy / penugasan saat menggunakanx
) tetapi versi kedua memindahkan semantik (std::move(x)
sebagai gantinya menulis , seperti dalam kode contoh). Jadi untuk pointer bersama, mode 1 dapat berguna untuk menghindari duplikasi kode.Mode 2: melewati smart pointer dengan referensi nilai yang dapat dimodifikasi (dapat dimodifikasi)
Di sini fungsinya hanya membutuhkan referensi yang dapat dimodifikasi untuk pointer cerdas, tetapi tidak memberikan indikasi apa yang akan dilakukan dengan itu. Saya ingin memanggil metode ini dengan kartu : penelepon memastikan pembayaran dengan memberikan nomor kartu kredit. Referensi dapat digunakan untuk mengambil kepemilikan objek runcing, tetapi tidak harus. Mode ini membutuhkan penyediaan argumen nilai yang dapat dimodifikasi, yang sesuai dengan fakta bahwa efek yang diinginkan dari fungsi tersebut termasuk meninggalkan nilai yang berguna dalam variabel argumen. Penelepon dengan ekspresi nilai yang ingin diteruskan ke fungsi seperti itu akan dipaksa untuk menyimpannya dalam variabel bernama untuk dapat melakukan panggilan, karena bahasa hanya menyediakan konversi implisit ke konstantareferensi nilai (mengacu pada sementara) dari suatu nilai. (Tidak seperti situasi sebaliknya ditangani oleh
std::move
, cor dariY&&
keY&
, denganY
jenis pointer cerdas, tidak mungkin; tetap konversi ini bisa diperoleh dengan fungsi template sederhana jika benar-benar diinginkan, lihat https://stackoverflow.com/a/24868376 / 1436796 ). Untuk kasus di mana fungsi yang dipanggil bermaksud mengambil kepemilikan objek tanpa syarat, mencuri dari argumen, kewajiban untuk memberikan argumen nilai lv memberikan sinyal yang salah: variabel tidak akan memiliki nilai berguna setelah panggilan. Karena itu mode 3, yang memberikan kemungkinan identik di dalam fungsi kita tetapi meminta penelepon untuk memberikan nilai, harus dipilih untuk penggunaan tersebut.Namun ada kasus penggunaan yang valid untuk mode 2, yaitu fungsi yang dapat memodifikasi pointer, atau objek yang menunjuk dengan cara yang melibatkan kepemilikan . Misalnya, fungsi yang awalan sebuah simpul ke sebuah
list
memberikan contoh penggunaan tersebut:Jelas tidak diinginkan di sini untuk memaksa penelepon untuk menggunakan
std::move
, karena penunjuk pintar mereka masih memiliki daftar yang jelas dan tidak kosong setelah panggilan, meskipun berbeda dari sebelumnya.Sekali lagi, menarik untuk mengamati apa yang terjadi jika
prepend
panggilan gagal karena kekurangan memori. Makanew
panggilan akan dibuangstd::bad_alloc
; pada titik waktu ini, karena tidaknode
dapat dialokasikan, dapat dipastikan bahwa referensi nilai yang lewat (mode 3) daristd::move(l)
belum dapat dicuri, karena itu akan dilakukan untuk membangunnext
bidangnode
yang gagal dialokasikan. Jadi smart pointer aslil
masih menyimpan daftar asli ketika kesalahan dilemparkan; daftar itu akan dimusnahkan dengan baik oleh destruktor penunjuk pintar, atau jikal
seandainya bertahan hidup berkatcatch
klausa awal yang cukup , itu masih akan memegang daftar asli.Itu adalah contoh konstruktif; dengan mengedipkan mata ke pertanyaan ini orang juga dapat memberikan contoh yang lebih destruktif dari menghapus node pertama yang berisi nilai yang diberikan, jika ada:
Sekali lagi kebenarannya cukup halus di sini. Khususnya, dalam pernyataan akhir, pointer yang
(*p)->next
disimpan di dalam node yang akan dihapus tidak terhubung (olehrelease
, yang mengembalikan pointer tetapi membuat null asli) sebelumreset
(secara implisit) menghancurkan node itu (ketika itu menghancurkan nilai lama yang dipegang olehp
), memastikan bahwa satu dan hanya satu simpul yang hancur pada saat itu. (Dalam bentuk alternatif yang disebutkan dalam komentar, waktu ini akan diserahkan kepada internal implementasi operator penugasan pindahstd::unique_ptr
instancelist
; standar mengatakan 20.7.1.2.3; 2 bahwa operator ini harus bertindak "seolah-olah oleh memanggilreset(u.release())
", kapan waktunya harus aman di sini juga.)Perhatikan bahwa
prepend
danremove_first
tidak dapat dipanggil oleh klien yang menyimpannode
variabel lokal untuk daftar yang selalu kosong, dan memang benar karena implementasi yang diberikan tidak dapat berfungsi untuk kasus-kasus seperti itu.Mode 3: melewati pointer cerdas dengan referensi nilai (dapat dimodifikasi)
Ini adalah mode yang lebih disukai untuk digunakan ketika hanya mengambil kepemilikan pointer. Saya ingin memanggil metode ini dengan cek : penelepon harus menerima pelepasan kepemilikan, seolah-olah memberikan uang tunai, dengan menandatangani cek, tetapi penarikan yang sebenarnya ditunda hingga fungsi yang dipanggil benar-benar menusuk pointer (persis seperti ketika menggunakan mode 2) ). "Penandatanganan cek" secara konkret berarti penelepon harus membungkus argumen dalam
std::move
(seperti dalam mode 1) jika itu adalah nilai rendah (jika itu adalah nilai, bagian "menyerahkan kepemilikan" jelas dan tidak memerlukan kode terpisah).Perhatikan bahwa secara teknis mode 3 berperilaku persis seperti mode 2, sehingga fungsi yang dipanggil tidak harus mengambil alih kepemilikan; Namun saya akan bersikeras bahwa jika ada ketidakpastian tentang transfer kepemilikan (dalam penggunaan normal), mode 2 harus lebih suka mode 3, sehingga menggunakan mode 3 secara implisit sinyal untuk penelepon bahwa mereka yang menyerah kepemilikan. Orang mungkin membalas bahwa hanya mode 1 argumen yang lewat benar-benar menandakan hilangnya kepemilikan oleh penelepon. Tetapi jika klien memiliki keraguan tentang niat dari fungsi yang dipanggil, dia seharusnya mengetahui spesifikasi fungsi yang dipanggil, yang seharusnya menghilangkan keraguan.
Sangatlah sulit untuk menemukan contoh khas yang melibatkan
list
tipe kita yang menggunakan mode 3 argumen yang lewat. Memindahkan daftarb
ke akhir daftar laina
adalah contoh umum; namuna
(yang bertahan dan menahan hasil operasi) lebih baik dilewatkan menggunakan mode 2:Contoh murni dari mode 3 argumen yang lewat adalah berikut ini yang mengambil daftar (dan kepemilikannya), dan mengembalikan daftar yang berisi node identik dalam urutan terbalik.
Fungsi ini bisa disebut sebagai
l = reversed(std::move(l));
untuk membalik daftar menjadi dirinya sendiri, tetapi daftar terbalik juga dapat digunakan secara berbeda.Di sini argumen segera dipindahkan ke variabel lokal untuk efisiensi (orang bisa menggunakan parameter
l
langsung di tempatp
, tetapi kemudian mengaksesnya setiap kali akan melibatkan tingkat tipuan ekstra); maka perbedaan dengan mode 1 argumen yang lewat adalah minimal. Sebenarnya menggunakan mode itu, argumen bisa berfungsi langsung sebagai variabel lokal, sehingga menghindari langkah awal itu; ini hanya contoh dari prinsip umum bahwa jika argumen yang dilewatkan oleh referensi hanya berfungsi untuk menginisialisasi variabel lokal, orang mungkin juga meneruskannya dengan nilai sebagai gantinya dan menggunakan parameter sebagai variabel lokal.Menggunakan mode 3 nampaknya didukung oleh standar, seperti yang disaksikan oleh fakta bahwa semua fungsi pustaka yang disediakan yang mentransfer kepemilikan smart pointer menggunakan mode 3. Kasus meyakinkan tertentu adalah konstruktor
std::shared_ptr<T>(auto_ptr<T>&& p)
. Konstruktor itu digunakan (dalamstd::tr1
) untuk mengambil referensi lvalue yang dapat dimodifikasi (sama sepertiauto_ptr<T>&
copy constructor), dan karenanya dapat dipanggil denganauto_ptr<T>
lvaluep
seperti padastd::shared_ptr<T> q(p)
, setelahp
itu telah disetel ulang ke nol. Karena perubahan dari mode 2 ke 3 dalam melewati argumen, kode lama ini sekarang harus ditulis ulangstd::shared_ptr<T> q(std::move(p))
dan kemudian akan terus bekerja. Saya mengerti bahwa panitia tidak menyukai mode 2 di sini, tetapi mereka memiliki opsi untuk mengubah ke mode 1, dengan mendefinisikanstd::shared_ptr<T>(auto_ptr<T> p)
sebagai gantinya, mereka dapat memastikan bahwa kode lama berfungsi tanpa modifikasi, karena (tidak seperti pointer unik) pointer otomatis dapat secara diam-diam direferensikan ke nilai (objek pointer itu sendiri diatur ulang ke nol dalam proses). Rupanya panitia lebih menyukai advokasi mode 3 daripada mode 1, sehingga mereka memilih untuk secara aktif memecahkan kode yang ada daripada menggunakan mode 1 bahkan untuk penggunaan yang sudah usang.Kapan memilih mode 3 daripada mode 1
Mode 1 benar-benar dapat digunakan dalam banyak kasus, dan mungkin lebih disukai daripada mode 3 dalam kasus di mana asumsi kepemilikan akan mengambil bentuk memindahkan pointer pintar ke variabel lokal seperti pada
reversed
contoh di atas. Namun, saya dapat melihat dua alasan untuk memilih mode 3 dalam kasus yang lebih umum:Ini sedikit lebih efisien untuk melewati referensi daripada membuat sementara dan nix pointer lama (penanganan uang tunai agak melelahkan); dalam beberapa skenario pointer mungkin dilewatkan beberapa kali tidak berubah ke fungsi lain sebelum benar-benar dicuri. Melewati seperti itu umumnya akan memerlukan penulisan
std::move
(kecuali mode 2 digunakan), tetapi perhatikan bahwa ini hanya pemain yang tidak benar-benar melakukan apa-apa (khususnya tanpa dereferencing), sehingga tidak ada biaya tambahan.Haruskah dibayangkan bahwa apa pun melempar pengecualian antara awal panggilan fungsi dan titik di mana itu (atau beberapa panggilan yang terkandung) benar-benar memindahkan objek yang diarahkan ke ke dalam struktur data lain (dan pengecualian ini belum terperangkap di dalam fungsi itu sendiri ), maka ketika menggunakan mode 1, objek yang dirujuk oleh smart pointer akan dihancurkan sebelum
catch
klausa dapat menangani pengecualian (karena parameter fungsi dirusak selama stack unwinding), tetapi tidak demikian ketika menggunakan mode 3. Yang terakhir memberikan penelepon memiliki opsi untuk memulihkan data objek dalam kasus tersebut (dengan menangkap pengecualian). Perhatikan bahwa mode 1 di sini tidak menyebabkan kebocoran memori , tetapi dapat menyebabkan hilangnya data yang tidak dapat dipulihkan untuk program, yang mungkin juga tidak diinginkan.Mengembalikan penunjuk cerdas: selalu berdasarkan nilai
Untuk menyimpulkan kata tentang mengembalikan pointer cerdas, mungkin menunjuk ke objek yang dibuat untuk digunakan oleh pemanggil. Ini sebenarnya bukan kasus yang sebanding dengan melewatkan pointer ke fungsi, tetapi untuk kelengkapan saya ingin menegaskan bahwa dalam kasus seperti itu selalu kembali dengan nilai (dan tidak digunakan
std::move
dalamreturn
pernyataan). Tidak ada yang ingin mendapatkan referensi ke pointer yang mungkin baru saja di-nixed.sumber
unique_ptr
telah dipindahkan-dari atau tidak, itu masih akan menghapus nilai dengan baik jika masih menyimpannya setiap kali dihancurkan atau digunakan kembali.unique_ptr
mencegah kebocoran memori (dan dengan demikian dalam arti memenuhi kontraknya), tetapi di sini (yaitu, menggunakan mode 1) itu dapat menyebabkan (dalam keadaan tertentu) sesuatu yang mungkin dianggap lebih berbahaya , yaitu hilangnya data (penghancuran nilai yang ditunjukkan) yang bisa dihindari menggunakan mode 3.Ya, Anda harus melakukannya jika mengambil
unique_ptr
nilai dalam konstruktor. Kejelasan adalah hal yang baik. Karena tidakunique_ptr
dapat disalin (private copy ctor), apa yang Anda tulis harus memberi Anda kesalahan kompiler.sumber
Sunting: Jawaban ini salah, meskipun, sebenarnya kode itu berfungsi. Saya hanya meninggalkannya di sini karena diskusi di bawahnya terlalu berguna. Jawaban lain ini adalah jawaban terbaik yang diberikan pada saat saya terakhir mengedit ini: Bagaimana cara saya melewatkan argumen unique_ptr ke konstruktor atau fungsi?
Ide dasarnya
::std::move
adalah bahwa orang-orang yang melewati Andaunique_ptr
harus menggunakannya untuk mengungkapkan pengetahuan bahwa mereka tahuunique_ptr
mereka lewat akan kehilangan kepemilikan.Ini berarti Anda harus menggunakan referensi nilai untuk
unique_ptr
dalam metode Anda, bukanunique_ptr
itu sendiri. Lagipula ini tidak akan berhasil karena melewati yang lama biasaunique_ptr
akan memerlukan membuat salinan, dan itu secara eksplisit dilarang untuk antarmukaunique_ptr
. Cukup menarik, menggunakan referensi nilai bernama mengubahnya kembali menjadi nilai lagi, jadi Anda perlu menggunakan::std::move
di dalam metode Anda juga.Ini berarti dua metode Anda akan terlihat seperti ini:
Maka orang yang menggunakan metode akan melakukan ini:
Seperti yang Anda lihat,
::std::move
ekspresi bahwa pointer akan kehilangan kepemilikan pada titik yang paling relevan dan membantu untuk diketahui. Jika ini terjadi tanpa terlihat, akan sangat membingungkan bagi orang-orang yang menggunakan kelas Anda untukobjptr
tiba - tiba kehilangan kepemilikan tanpa alasan yang jelas.sumber
Base fred(::std::move(objptr));
dan tidakBase::UPtr fred(::std::move(objptr));
?std::move
implementasi konstruktor dan metode. Dan bahkan ketika Anda melewati nilai, penelepon masih harus menggunakanstd::move
untuk melewati nilai. Perbedaan utama adalah bahwa dengan pass-by-value antarmuka membuat kepemilikan yang jelas akan hilang. Lihat komentar Nicol Bolas tentang jawaban lain.harus jauh lebih baik
dan
seharusnya
dengan tubuh yang sama.
Dan ... apa yang
evt
dihandle()
??sumber
std::forward
disini:Base::UPtr&&
adalah selalu jenis referensi nilai p, danstd::move
dibagikan sebagai nilai p. Sudah diteruskan dengan benar.unique_ptr
nilai, maka Anda dijamin bahwa constructor bergerak dipanggil pada nilai baru (atau hanya bahwa Anda diberi sementara). Ini memastikan bahwaunique_ptr
variabel yang dimiliki pengguna sekarang kosong . Jika Anda menerimanya&&
, itu hanya akan dikosongkan jika kode Anda meminta operasi pemindahan. Cara Anda, adalah mungkin untuk variabel yang tidak harus dipindahkan dari pengguna. Yang membuat penggunastd::move
tersangka dan membingungkan. Penggunaanstd::move
harus selalu memastikan bahwa sesuatu telah dipindahkan .Ke bagian atas memilih jawaban. Saya lebih suka melewati referensi nilai.
Saya mengerti apa masalah yang disebabkan oleh rvalue yang disebabkan oleh referensi. Tapi mari kita bagi masalah ini menjadi dua sisi:
Saya harus menulis kode
Base newBase(std::move(<lvalue>))
atauBase newBase(<rvalue>)
.Penulis perpustakaan harus menjamin itu benar-benar akan memindahkan unique_ptr untuk menginisialisasi anggota jika ingin memiliki kepemilikan.
Itu saja.
Jika Anda melewati referensi nilai, itu hanya akan memanggil satu instruksi "pindah", tetapi jika lulus dengan nilai, itu adalah dua.
Yap, jika penulis perpustakaan tidak ahli tentang ini, ia mungkin tidak memindahkan unique_ptr untuk menginisialisasi anggota, tapi itu masalah penulis, bukan Anda. Apa pun yang melewati nilai atau referensi nilai, kode Anda sama!
Jika Anda sedang menulis perpustakaan, sekarang Anda tahu Anda harus menjaminnya, jadi lakukan saja, melewati referensi nilai adalah pilihan yang lebih baik daripada nilai. Klien yang menggunakan pustaka Anda hanya akan menulis kode yang sama.
Sekarang, untuk pertanyaan Anda. Bagaimana cara meneruskan argumen unique_ptr ke konstruktor atau fungsi?
Anda tahu apa pilihan terbaik.
http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html
sumber