C ++ Sự khác biệt giữa std :: ref (T) và T &?


92

Tôi có một số câu hỏi liên quan đến chương trình này:

#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;
}

Đầu ra là:

false

Tôi muốn biết, nếu std::refkhông trả về tham chiếu của một đối tượng, thì nó sẽ làm gì? Về cơ bản, sự khác biệt giữa:

T x;
auto r = ref(x);

T x;
T &y = x;

Ngoài ra, tôi muốn biết tại sao lại có sự khác biệt này? Tại sao chúng ta cần std::refhoặc std::reference_wrapperkhi chúng ta có tài liệu tham khảo (tức là T&)?



Gợi ý: điều gì xảy ra nếu bạn làm x = y; trong cả hai trường hợp?
juanchopanza

2
Ngoài cờ trùng lặp của tôi (nhận xét cuối cùng): Xem ví dụ: stackoverflow.com/questions/31270810/…stackoverflow.com/questions/26766939/…
anderas

2
@anderas nó không phải về tính hữu dụng, đó là về sự khác biệt chủ yếu
CppNITR

@CppNITR Sau đó, xem các câu hỏi tôi đã liên kết vài giây trước nhận xét của bạn. Đặc biệt là cái thứ hai là hữu ích.
anderas

Câu trả lời:


91

refCấu trúc tốt một đối tượng có reference_wrapperkiểu thích hợp để chứa một tham chiếu đến một đối tượng. Có nghĩa là khi bạn đăng ký:

auto r = ref(x);

Điều này trả về a reference_wrappervà không phải là tham chiếu trực tiếp đến x(tức là T&). Điều này reference_wrapper(tức là r) thay vì giữ T&.

A reference_wrapperrất hữu ích khi bạn muốn mô phỏng một referenceđối tượng có thể được sao chép (nó vừa có thể sao chép-xây dựng vừa có thể sao chép-gán được ).

Trong C ++, khi bạn tạo một tham chiếu (nói y) đến một đối tượng (nói x), sau đó yxchia sẻ cùng một địa chỉ cơ sở . Hơn nữa, ykhông thể tham chiếu đến bất kỳ đối tượng nào khác. Ngoài ra, bạn không thể tạo một mảng tham chiếu, tức là mã như thế này sẽ gây ra lỗi:

#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;
}

Tuy nhiên điều này là hợp pháp:

#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
*/

Nói về vấn đề của bạn với cout << is_same<T&,decltype(r)>::value;, giải pháp là:

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

Hãy để tôi cho bạn xem một chương trình:

#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
*/

Xem ở đây, chúng ta biết ba điều:

  1. Một reference_wrapperđối tượng (ở đây r) có thể được sử dụng để tạo một mảng tham chiếu mà không thể với T&.

  2. rthực sự hoạt động như một tham chiếu thực (xem r.get()=70giá trị của đã thay đổi như thế nào y).

  3. rkhông giống như T&nhưng r.get()là. Điều này có nghĩa rằng rgiữ T&nghĩa như tên gọi của nó cho thấy là một wrapper xung quanh một tài liệu tham khảo T& .

Tôi hy vọng câu trả lời này là quá đủ để giải thích những nghi ngờ của bạn.


3
1: Không, a reference_wrappercó thể được gán lại , nhưng nó không thể "chứa tham chiếu đến nhiều hơn một đối tượng". 2/3: Điểm .get()hợp lý về vị trí phù hợp - nhưng không trộn lẫn r có thể được sử dụng giống như T&trong trường hợp rchuyển đổi của operatorcó thể được gọi một cách rõ ràng - vì vậy không cần gọi .get()trong nhiều trường hợp, bao gồm một số trường hợp trong mã của bạn (rất khó đọc do thiếu khoảng trống).
underscore_d

@underscore_d reference_wrappercó thể chứa một loạt các tham chiếu nếu bạn không chắc chắn thì bạn có thể tự mình thử. Dấu cộng .get()được sử dụng khi bạn muốn thay đổi giá trị của đối tượng mà reference_wrappernó đang giữ` tức r=70là bất hợp pháp nên bạn phải sử dụng r.get()=70. Cố gắng lên nào !!!!!!
Ankit Acharya

1
Không hẳn vậy. Đầu tiên, hãy sử dụng từ ngữ chính xác. Bạn đang hiển thị một reference_wrapper chứa một tham chiếu đến một mảng - không phải một reference_wrapper mà bản thân nó chứa "tham chiếu đến nhiều đối tượng" . Trình bao bọc chỉ chứa một tham chiếu. Thứ hai, tôi có thể nhận được một tham chiếu gốc đến một mảng - bạn có chắc là bạn không quên (dấu ngoặc đơn) xung quanh int a[4]{1, 2, 3, 4}; int (&b)[4] = a;không? reference_wrapper không đặc biệt ở đây vì bản địa T& chỉ hoạt động.
underscore_d

1
@AnkitAcharya Có :-) nhưng nói chính xác, sang một bên kết quả hiệu quả, bản thân nó chỉ đề cập đến một đối tượng. Dù sao, bạn tất nhiên đúng rằng không giống như một ref bình thường, wrappercó thể đi trong một thùng chứa. Điều này rất hữu ích, nhưng tôi nghĩ mọi người hiểu sai rằng điều này là cao cấp hơn so với thực tế. Nếu tôi muốn có một mảng 'refs', tôi thường bỏ qua người trung gian vector<Item *>, đó là thứ mà tôi wrappermuốn nói ... và hy vọng những người theo chủ nghĩa chống trỏ không tìm thấy tôi. Các trường hợp sử dụng thuyết phục cho nó là khác nhau và phức tạp hơn.
underscore_d

1
"Reference_wrapper rất hữu ích khi bạn muốn mô phỏng một tham chiếu của một đối tượng có thể được sao chép." Nhưng điều đó không đánh bại mục đích của một tài liệu tham khảo? Bạn đang đề cập đến điều thực tế. Đó là những gì một IS tham chiếu. Một tham chiếu có thể được sao chép, dường như vô nghĩa, bởi vì nếu bạn muốn sao chép nó, thì ngay từ đầu bạn không muốn một tham chiếu, bạn chỉ nên sao chép đối tượng gốc. Nó có vẻ như một lớp phức tạp không cần thiết để giải quyết một vấn đề mà ngay từ đầu không cần phải tồn tại.
stu

53

std::reference_wrapper được công nhận bởi các cơ sở tiêu chuẩn để có thể chuyển các đối tượng bằng cách tham chiếu trong các ngữ cảnh truyền theo giá trị.

Ví dụ: std::bindcó thể lấy std::ref()một thứ gì đó, truyền nó theo giá trị và giải nén nó trở lại thành một tham chiếu sau này.

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();
}

Đoạn mã này xuất ra:

10
20

Giá trị của iđã được lưu trữ (lấy theo giá trị) f1tại thời điểm nó được khởi tạo, nhưng f2đã giữ một std::reference_wrappergiá trị theo giá trị, và do đó hoạt động giống như khi lấy giá trị int&.


2
@CppNITR chắc chắn! Hãy cho tôi một chút thời gian để lắp ráp một bản demo nhỏ :)
Quentin

1
những gì về sự khác biệt giữa T & & ref (T)
CppNITR

3
@CppNITR std::ref(T)trả về a std::reference_wrapper. Nó không hơn một con trỏ được bọc, nhưng được thư viện công nhận là "này, tôi phải là tài liệu tham khảo! Vui lòng chuyển tôi trở lại sau khi bạn đưa tôi đi xem xong".
Quentin

38

Tham chiếu ( T&hoặc T&&) là một phần tử đặc biệt trong ngôn ngữ C ++. Nó cho phép thao tác một đối tượng bằng cách tham chiếu và có các trường hợp sử dụng đặc biệt trong ngôn ngữ. Ví dụ: bạn không thể tạo một vùng chứa tiêu chuẩn để chứa các tham chiếu: vector<T&>không được hình thành và tạo ra lỗi biên dịch.

std::reference_wrapperMặt khác, A là một đối tượng C ++ có thể chứa một tham chiếu. Như vậy, bạn có thể sử dụng nó trong các thùng chứa tiêu chuẩn.

std::reflà một hàm tiêu chuẩn trả về một std::reference_wrapperđối số của nó. Trong cùng một ý tưởng, std::creftrả về std::reference_wrappermột tham chiếu const.

Một thuộc tính thú vị của a std::reference_wrapper, là nó có một operator T& () const noexcept;. Điều đó có nghĩa là ngay cả khi nó là một đối tượng true , nó có thể được tự động chuyển đổi thành tham chiếu mà nó đang giữ. Vì thế:

  • vì nó là một đối tượng có thể gán bản sao, nó có thể được sử dụng trong các vùng chứa hoặc trong các trường hợp khác mà các tham chiếu không được phép
  • nhờ nó operator T& () const noexcept;, nó có thể được sử dụng ở bất cứ đâu bạn có thể sử dụng một tham chiếu, bởi vì nó sẽ được tự động chuyển đổi sang nó.

1
chủ yếu được ủng hộ vì không đề cập đến operator T& ()2 câu trả lời khác.
metablaster
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.