Perbedaan antara std :: reference_wrapper dan simple pointer?

99

Mengapa ada kebutuhan untuk memiliki std::reference_wrapper? Dimana harus digunakan? Apa bedanya dengan penunjuk sederhana? Bagaimana performanya dibandingkan dengan pointer sederhana?

Laurynas Lazauskas
sumber
4
Ini pada dasarnya adalah penunjuk yang Anda gunakan .dengan alih-alih->
MM
5
@MM Tidak, penggunaan .tidak berfungsi seperti yang Anda sarankan (kecuali pada titik tertentu proposal titik operator diadopsi dan diintegrasikan :))
Columbo
3
Pertanyaan seperti inilah yang membuat saya tidak senang ketika harus bekerja dengan C ++ baru.
Nils
Untuk menindaklanjuti Columbo, std :: reference_wrapper digunakan dengan get()fungsi-anggotanya atau dengan konversi implisitnya kembali ke tipe yang mendasarinya.
Max Barraclough

Jawaban:

88

std::reference_wrapperberguna jika dikombinasikan dengan template. Ini membungkus objek dengan menyimpan penunjuk ke sana, memungkinkan untuk penugasan ulang dan menyalin sambil meniru semantik biasanya. Ini juga menginstruksikan templat perpustakaan tertentu untuk menyimpan referensi, bukan objek.

Pertimbangkan algoritme di STL yang menyalin functor: Anda dapat menghindari penyalinan itu hanya dengan meneruskan pembungkus referensi yang merujuk ke functor alih-alih functor itu sendiri:

unsigned arr[10];
std::mt19937 myEngine;
std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine's state

Ini berfungsi karena…

  • reference_wrapperS overloadoperator() sehingga mereka dapat dipanggil seperti objek fungsi yang dirujuknya:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
  • … (Tidak) seperti referensi biasa, menyalin (dan menetapkan) reference_wrappershanya menugaskan orang yang ditunjuk.

    int i, j;
    auto r = std::ref(i); // r refers to i
    r = std::ref(j); // Okay; r refers to j
    r = std::cref(j); // Error: Cannot bind reference_wrapper<int> to <const int>
    

Menyalin pembungkus referensi secara praktis sama dengan menyalin penunjuk, yang semurah harganya. Semua pemanggilan fungsi yang melekat dalam penggunaannya (misalnya yang ke operator()) harus hanya sebaris karena merupakan satu baris.

reference_wrappers dibuat melalui std::refdanstd::cref :

int i;
auto r = std::ref(i); // r is of type std::reference_wrapper<int>
auto r2 = std::cref(i); // r is of type std::reference_wrapper<const int>

Argumen template menentukan tipe dan kualifikasi cv dari objek yang dirujuk; r2mengacu pada a const intdan hanya akan menghasilkan referensi ke const int. Panggilan ke pembungkus referensi dengan fungsi constdi dalamnya hanya akan memanggil constfungsi anggota operator().

Rvalue penginisialisasi tidak diizinkan, karena mengizinkannya akan lebih merugikan daripada menguntungkan. Karena rvalues ​​akan dipindahkan bagaimanapun juga (dan dengan penghapusan salinan yang dijamin bahkan itu dihindari sebagian), kami tidak meningkatkan semantik; kami dapat memperkenalkan petunjuk yang menjuntai, karena pembungkus referensi tidak memperpanjang umur penerima.

Interaksi perpustakaan

Seperti disebutkan sebelumnya, seseorang dapat menginstruksikan make_tupleuntuk menyimpan referensi dalam hasil tupledengan meneruskan argumen terkait melalui reference_wrapper:

int i;
auto t1 = std::make_tuple(i); // Copies i. Type of t1 is tuple<int>
auto t2 = std::make_tuple(std::ref(i)); // Saves a reference to i.
                                        // Type of t2 is tuple<int&>

Perhatikan bahwa ini sedikit berbeda dari forward_as_tuple: Di sini, nilai r sebagai argumen tidak diperbolehkan.

std::bindmenunjukkan perilaku yang sama: Ini tidak akan menyalin argumen tetapi menyimpan referensi jika itu adalah reference_wrapper. Berguna jika argumen itu (atau functor!) Tidak perlu disalin tetapi tetap dalam ruang lingkup sementara bind-functor digunakan.

Perbedaan dari petunjuk biasa

  • Tidak ada tingkat tambahan dari tipuan sintaksis. Pointer harus direferensikan untuk mendapatkan nilai l ke objek yang dirujuk; reference_wrappers memiliki operator konversi implisit dan dapat dipanggil seperti objek yang dibungkusnya.

    int i;
    int& ref = std::ref(i); // Okay
    
  • reference_wrappers, tidak seperti pointer, tidak memiliki status null. Mereka harus diinisialisasi dengan referensi atau lainnyareference_wrapper .

    std::reference_wrapper<int> r; // Invalid
  • Kesamaannya adalah semantik salinan yang dangkal: Pointer dan reference_wrappers dapat ditetapkan ulang.

Columbo
sumber
Apakah std::make_tuple(std::ref(i));lebih unggul std::make_tuple(&i);dalam beberapa hal?
Laurynas Lazauskas
6
@LaurynasLazauskas Ini berbeda. Yang terakhir yang Anda tunjukkan menyimpan penunjuk ke i, bukan referensi ke sana.
Columbo
Hm ... Kurasa aku masih belum bisa membedakan keduanya sebaik yang aku mau ... Terima kasih.
Laurynas Lazauskas
@ Columbo Bagaimana array pembungkus referensi mungkin jika mereka tidak memiliki status null? Bukankah array biasanya dimulai dengan semua elemen disetel ke status null?
anatolyg
2
@anatolyg Apa yang menghalangi Anda untuk menginisialisasi array itu?
Columbo
27

Setidaknya ada dua tujuan motivasi dari std::reference_wrapper<T>:

  1. Ini untuk memberikan semantik referensi ke objek yang diteruskan sebagai parameter nilai ke template fungsi. Misalnya, Anda mungkin memiliki objek fungsi besar yang ingin Anda teruskan std::for_each()yang mengambil parameter objek fungsinya berdasarkan nilai. Untuk menghindari penyalinan objek, Anda dapat menggunakan

    std::for_each(begin, end, std::ref(fun));

    Melewati argumen sebagai std::reference_wrapper<T>sebuah std::bind()ekspresi cukup umum untuk mengikat argumen dengan referensi bukan oleh nilai.

  2. Saat menggunakan std::reference_wrapper<T>dengan std::make_tuple()elemen tupel yang sesuai menjadi T&bukan T:

    T object;
    f(std::make_tuple(1, std::ref(object)));
    
Dietmar Kühl
sumber
Bisakah Anda memberikan contoh kode untuk kasus pertama?
pengguna1708860
1
@ user1708860: maksud Anda selain yang diberikan ...?
Dietmar Kühl
Maksud saya kode aktual yang sesuai dengan std :: ref (menyenangkan) karena saya tidak mengerti bagaimana digunakan (kecuali kesenangan adalah objek dan bukan fungsi ...)
user1708860
2
@ user1708860: ya, kemungkinan besar funadalah objek fungsi (yaitu objek kelas dengan operator pemanggil fungsi) dan bukan fungsi: jika funkebetulan merupakan fungsi sebenarnya, std::ref(fun)tidak memiliki tujuan dan membuat kode berpotensi lebih lambat.
Dietmar Kühl
23

Perbedaan lain, dalam hal kode yang mendokumentasikan diri, adalah bahwa menggunakan pada reference_wrapperdasarnya mengingkari kepemilikan objek. Sebaliknya, a unique_ptrmenegaskan kepemilikan, sementara pointer kosong mungkin atau mungkin tidak dimiliki (tidak mungkin untuk mengetahuinya tanpa melihat banyak kode terkait):

vector<int*> a;                    // the int values might or might not be owned
vector<unique_ptr<int>> b;         // the int values are definitely owned
vector<reference_wrapper<int>> c;  // the int values are definitely not owned
Edward Loper
sumber
3
kecuali itu adalah kode pre-c ++ 11, contoh pertama harus menyiratkan nilai opsional yang tidak dimiliki, misalnya untuk pencarian cache berdasarkan indeks. Alangkah baiknya jika std memberi kami sesuatu yang standar untuk mewakili nilai yang dimiliki non-null (varian unik & bersama)
Bwmat
Ini mungkin tidak sepenting di C ++ 11, di mana pointer kosong hampir selalu merupakan nilai pinjaman.
Elling
reference_wrapperlebih unggul dari petunjuk mentah tidak hanya karena jelas bahwa itu bukan kepemilikan, tetapi juga karena tidak dapat nullptr(tanpa kejahatan) dan dengan demikian pengguna tahu mereka tidak dapat lewat nullptr(tanpa kejahatan) dan Anda tahu Anda tidak perlu melakukannya periksa itu.
underscore_d
19

Anda bisa menganggapnya sebagai pembungkus praktis di sekitar referensi sehingga Anda bisa menggunakannya dalam wadah.

std::vector<std::reference_wrapper<T>> vec; // OK - does what you want
std::vector<T&> vec2; // Nope! Will not compile

Ini pada dasarnya adalah CopyAssignableversi T&. Kapan pun Anda menginginkan referensi, tetapi harus dapat dialihkan, gunakan std::reference_wrapper<T>atau fungsi penolongnya std::ref(). Atau gunakan penunjuk.


Kebiasaan lainnya sizeof:

sizeof(std::reference_wrapper<T>) == sizeof(T*) // so 8 on a 64-bit box
sizeof(T&) == sizeof(T) // so, e.g., sizeof(vector<int>&) == 24

Dan perbandingan:

int i = 42;
assert(std::ref(i) == std::ref(i)); // ok

std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error
Barry
sumber
1
@LaurynasLazauskas Seseorang bisa memanggil objek fungsi yang terkandung dalam pembungkus secara langsung. Itu juga dijelaskan dalam jawaban saya.
Columbo
2
Karena implementasi referensi hanya penunjuk di dalam, saya tidak mengerti mengapa pembungkus menambahkan tipuan atau penalti kinerja
Riga
4
Seharusnya tidak ada lagi tipuan daripada referensi sederhana ketika datang ke kode rilis
Riga
3
Saya mengharapkan kompiler untuk menyebariskan reference_wrapperkode sepele , membuatnya identik dengan kode yang menggunakan pointer atau referensi.
David Stone
4
@LaurynasLazauskas: std::reference_wrappermemiliki jaminan bahwa objek tersebut tidak pernah nol. Pertimbangkan seorang anggota kelas std::vector<T *>. Anda harus memeriksa semua kode kelas untuk melihat apakah objek ini dapat menyimpan a nullptrdalam vektor, sedangkan dengan std::reference_wrapper<T>, Anda dijamin memiliki objek yang valid.
David Stone