C ++ Perbedaan antara std :: ref (T) dan T &?

92

Saya punya beberapa pertanyaan tentang program ini:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
template <typename T> void foo ( T x )
{
    auto r=ref(x);
    cout<<boolalpha;
    cout<<is_same<T&,decltype(r)>::value;
}
int main()
{
    int x=5;
    foo (x);
    return 0;
}

Outputnya adalah:

false

Saya ingin tahu, jika std::reftidak mengembalikan referensi suatu objek, lalu apa fungsinya? Pada dasarnya apa perbedaan antara:

T x;
auto r = ref(x);

dan

T x;
T &y = x;

Juga, saya ingin tahu mengapa perbedaan ini ada? Mengapa kita membutuhkan std::refatau std::reference_wrapperketika kita memiliki referensi (yaitu T&)?

CppNITR
sumber
2
Kemungkinan duplikat dari Bagaimana tr1 :: reference_wrapper berguna?
anderas
Petunjuk: apa yang terjadi jika Anda melakukan x = y; kedua kasus tersebut?
juanchopanza
2
Selain bendera duplikat saya (komentar terakhir): Lihat misalnya stackoverflow.com/questions/31270810/… dan stackoverflow.com/questions/26766939/…
anderas
2
@anderas ini bukan tentang kegunaan, ini tentang perbedaan terutama
CppNITR
@CppNITR Kemudian lihat pertanyaan yang saya tautkan beberapa detik sebelum komentar Anda. Terutama yang kedua berguna.
anderas

Jawaban:

91

Well refmembangun sebuah objek dengan reference_wrappertipe yang sesuai untuk menampung referensi ke sebuah objek. Yang artinya saat Anda melamar:

auto r = ref(x);

Ini mengembalikan a reference_wrapperdan bukan referensi langsung ke x(yaitu T&). Ini reference_wrapper(yaitu r) sebagai gantinya berlaku T&.

A reference_wrappersangat berguna ketika Anda ingin meniru referenceobjek yang dapat disalin (keduanya dapat dikopi-konstruktif dan dapat dialihkan ).

Dalam C ++, setelah Anda membuat referensi (katakanlah y) ke obyek (katakanlah x), kemudian ydan xberbagi sama alamat dasar . Selain itu, ytidak dapat merujuk ke objek lain. Anda juga tidak dapat membuat array referensi, misalnya kode seperti ini akan menimbulkan kesalahan:

#include <iostream>
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    int& arr[] {x,y,z};    // error: declaration of 'arr' as array of references
    return 0;
}

Namun ini legal:

#include <iostream>
#include <functional>  // for reference_wrapper
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    reference_wrapper<int> arr[] {x,y,z};
    for (auto a: arr)
        cout << a << " ";
    return 0;
}
/* OUTPUT:
5 7 8
*/

Berbicara tentang masalah Anda dengan cout << is_same<T&,decltype(r)>::value;, solusinya adalah:

cout << is_same<T&,decltype(r.get())>::value;  // will yield true

Mari saya tunjukkan sebuah program:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;

int main()
{
    cout << boolalpha;
    int x=5, y=7;
    reference_wrapper<int> r=x;   // or auto r = ref(x);
    cout << is_same<int&, decltype(r.get())>::value << "\n";
    cout << (&x==&r.get()) << "\n";
    r=y;
    cout << (&y==&r.get()) << "\n";
    r.get()=70;
    cout << y;
    return 0;
}
/* Ouput:
true
true
true
70
*/

Lihat di sini kita mengetahui tiga hal:

  1. Sebuah reference_wrapperobjek (di sini r) dapat digunakan untuk membuat array referensi yang tidak memungkinkan T&.

  2. rsebenarnya bertindak seperti referensi nyata (lihat bagaimana r.get()=70mengubah nilai y).

  3. rtidak sama T&tetapi r.get(). Ini berarti bahwa rholding T&seperti namanya adalah pembungkus di sekitar referensi T& .

Saya harap jawaban ini lebih dari cukup untuk menjelaskan keraguan Anda.

Ankit Acharya
sumber
3
1: Tidak, a reference_wrapperdapat ditetapkan ulang , tetapi tidak dapat "menyimpan referensi ke lebih dari satu objek". 2/3: Poin yang adil tentang di mana .get()yang sesuai - tetapi tidak bercampur r dapat digunakan sama seperti T&dalam kasus di mana rkonversi operatordapat dipanggil dengan jelas - jadi tidak perlu memanggil .get()dalam banyak kasus, termasuk beberapa dalam kode Anda (yang sulit dibaca karena kurangnya ruang).
underscore_d
@underscore_d reference_wrapperdapat menyimpan berbagai referensi jika Anda tidak yakin maka Anda dapat mencobanya sendiri. Plus .get()digunakan ketika Anda ingin mengubah nilai objek yang reference_wrapperdipegang` yaitu r=70ilegal sehingga Anda harus menggunakannya r.get()=70. Coba sendiri !!!!!!
Ankit Acharya
1
Tidak juga. Pertama, mari gunakan kata-kata yang akurat. Anda menampilkan reference_wrapper yang menyimpan referensi ke larik - bukan reference_wrapper yang berisi "referensi ke lebih dari satu objek" . Pembungkus hanya menampung satu referensi. Kedua, saya bisa mendapatkan referensi native ke array dengan baik - apakah Anda yakin tidak hanya melupakan (tanda kurung) int a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper tidak istimewa di sini sejak asli T& tidak bekerja.
underscore_d
1
@AnkitAcharya Ya :-) tapi tepatnya, hasil yang efektif disamping, itu sendiri hanya mengacu pada satu objek. Bagaimanapun, Anda tentu benar bahwa tidak seperti wasit biasa, wrapperkaleng masuk dalam wadah. Ini berguna, tapi saya pikir orang salah menafsirkan ini sebagai lebih maju daripada yang sebenarnya. Jika saya menginginkan sebuah array dari 'refs', saya biasanya melewatkan perantara dengan vector<Item *>, itulah wrapperintinya ... dan berharap anti-pointer purists tidak menemukan saya. Kasus penggunaan yang meyakinkan untuk itu berbeda dan lebih kompleks.
underscore_d
1
"Reference_wrapper sangat berguna saat Anda ingin meniru referensi dari objek yang dapat disalin." Tapi bukankah itu menggagalkan tujuan referensi? Anda mengacu pada hal yang sebenarnya. Itulah referensi IS. Referensi yang bisa disalin, sepertinya tidak ada gunanya, karena jika Anda ingin menyalinnya, maka Anda tidak menginginkan referensi terlebih dahulu, Anda harus menyalin objek aslinya saja. Sepertinya lapisan kerumitan yang tidak perlu untuk menyelesaikan masalah yang sebenarnya tidak perlu ada.
stu
53

std::reference_wrapper dikenali oleh fasilitas standar untuk dapat melewatkan objek dengan referensi dalam konteks nilai-demi-nilai.

Misalnya, std::binddapat menerima std::ref()ke sesuatu, mengirimkannya berdasarkan nilai, dan mengekstraknya kembali menjadi referensi nanti.

void print(int i) {
    std::cout << i << '\n';
}

int main() {
    int i = 10;

    auto f1 = std::bind(print, i);
    auto f2 = std::bind(print, std::ref(i));

    i = 20;

    f1();
    f2();
}

Cuplikan ini menghasilkan:

10
20

Nilai itelah disimpan (diambil oleh nilai) ke f1dalam titik inisialisasi, tetapi f2telah disimpan std::reference_wrapperoleh nilai, dan dengan demikian berperilaku seperti yang diambil di file int&.

Quentin
sumber
2
@CppNITR yakin! Beri saya waktu untuk membuat demo kecil :)
Quentin
1
bagaimana dengan perbedaan antara T & & ref (T)
CppNITR
3
@CppNITR std::ref(T)mengembalikan a std::reference_wrapper. Ini sedikit lebih dari sebuah pointer yang dibungkus, tetapi dikenali oleh perpustakaan sebagai "hei, saya seharusnya menjadi referensi! Tolong ubah saya kembali menjadi satu setelah Anda selesai melewati saya".
Quentin
38

Referensi ( T&atau T&&) adalah elemen khusus dalam bahasa C ++. Ini memungkinkan untuk memanipulasi objek dengan referensi dan memiliki kasus penggunaan khusus dalam bahasa tersebut. Misalnya, Anda tidak dapat membuat penampung standar untuk menyimpan referensi: vector<T&>bentuknya tidak bagus dan menghasilkan kesalahan kompilasi.

A std::reference_wrapperdi sisi lain adalah objek C ++ yang dapat menampung referensi. Dengan demikian, Anda dapat menggunakannya dalam wadah standar.

std::refadalah fungsi standar yang mengembalikan a std::reference_wrapperpada argumennya. Dalam ide yang sama, std::crefkembali std::reference_wrapperke referensi const.

Salah satu properti yang menarik dari a std::reference_wrapper, adalah memiliki operator T& () const noexcept;. Itu berarti bahwa meskipun itu adalah objek yang sebenarnya , itu dapat secara otomatis diubah menjadi referensi yang dipegangnya. Begitu:

  • karena ini adalah objek yang dapat disalin, dapat digunakan dalam wadah atau dalam kasus lain di mana referensi tidak diperbolehkan
  • berkat sifatnya operator T& () const noexcept;, ini dapat digunakan di mana pun Anda dapat menggunakan referensi, karena itu akan secara otomatis dikonversi ke dalamnya.
Serge Ballesta
sumber
1
diberi suara positif terutama karena menyebutkan operator T& ()2 jawaban lainnya yang gagal disebutkan.
metablaster