C ++ ความแตกต่างระหว่าง std :: ref (T) และ T &?


92

ฉันมีคำถามเกี่ยวกับโปรแกรมนี้:

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

ผลลัพธ์คือ:

false

ฉันอยากรู้ว่าถ้าstd::refไม่ส่งคืนการอ้างอิงของวัตถุมันจะทำอย่างไร โดยทั่วไปความแตกต่างระหว่าง:

T x;
auto r = ref(x);

และ

T x;
T &y = x;

นอกจากนี้ฉันต้องการทราบว่าเหตุใดจึงมีความแตกต่างนี้ ทำไมเราถึงต้องการstd::refหรือstd::reference_wrapperเมื่อเรามีข้อมูลอ้างอิง (เช่นT&)?



คำแนะนำ: จะเกิดอะไรขึ้นถ้าคุณทำx = y; ทั้งสองกรณี?
juanchopanza

2
นอกเหนือจากแฟ
ล็กที่

2
@anderas มันไม่เกี่ยวกับประโยชน์ แต่มันเกี่ยวกับความแตกต่างเป็นหลัก
CppNITR

@CppNITR จากนั้นดูคำถามที่ฉันเชื่อมโยงไม่กี่วินาทีก่อนความคิดเห็นของคุณ โดยเฉพาะอย่างยิ่งข้อที่สองมีประโยชน์
anderas

คำตอบ:


91

ดีrefสร้างวัตถุของการที่เหมาะสมที่reference_wrapperประเภทที่จะถือการอ้างอิงไปยังวัตถุ ซึ่งหมายความว่าเมื่อคุณสมัคร:

auto r = ref(x);

สิ่งนี้ส่งคืน a reference_wrapperและไม่ใช่การอ้างอิงโดยตรงไปยังx(ie T&) นี้reference_wrapper(คือr) T&แทนถือ

A reference_wrapperมีประโยชน์มากเมื่อคุณต้องการเลียนแบบreferenceวัตถุที่สามารถคัดลอกได้ (สามารถคัดลอกได้ทั้งแบบสร้างได้และสามารถคัดลอกได้ )

ใน C ++ เมื่อคุณสร้างการอ้างอิง (พูดy) กับวัตถุ (พูดx) แล้วyและxร่วมกันอยู่ฐาน นอกจากนี้yไม่สามารถอ้างถึงวัตถุอื่นใด นอกจากนี้คุณไม่สามารถสร้างอาร์เรย์ของการอ้างอิงได้เช่นรหัสเช่นนี้จะทำให้เกิดข้อผิดพลาด:

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

อย่างไรก็ตามสิ่งนี้ถูกกฎหมาย:

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

เมื่อพูดถึงปัญหาของคุณcout << is_same<T&,decltype(r)>::value;วิธีแก้ปัญหาคือ:

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

ให้ฉันแสดงโปรแกรม:

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

ดูที่นี่เราทำความรู้จักกับสามสิ่ง:

  1. reference_wrapperวัตถุ (ที่นี่r) สามารถนำมาใช้ในการสร้างอาร์เรย์ของการอ้างอิงT&ซึ่งเป็นไปไม่ได้กับ

  2. rทำหน้าที่เหมือนการอ้างอิงจริง (ดูว่าr.get()=70ค่าของการเปลี่ยนแปลงy)

  3. rไม่เหมือนกับT&แต่r.get()เป็น วิธีการนี้ว่าrถือT&คือเป็นชื่อแนะนำเป็นห่อหุ้มรอบอ้างอิง T&

ฉันหวังว่าคำตอบนี้จะเพียงพอที่จะอธิบายข้อสงสัยของคุณ


3
1: ไม่reference_wrapperสามารถกำหนด a ใหม่ได้ แต่ไม่สามารถ "ระงับการอ้างอิงถึงมากกว่าหนึ่งวัตถุ" 2/3: จุดยุติธรรมเกี่ยวกับการที่.get()มีความเหมาะสม - แต่ unsuffixed r สามารถนำมาใช้เช่นเดียวกับT&ในกรณีที่r's แปลงoperatorสามารถเรียกอย่างไม่น่าสงสัย - จึงไม่จำเป็นที่จะเรียก.get()ในหลายกรณีรวมทั้งอีกหลายในรหัสของคุณ (ซึ่งเป็นเรื่องยากที่จะอ่าน เนื่องจากไม่มีช่องว่าง)
underscore_d

@underscore_d reference_wrapperสามารถเก็บข้อมูลอ้างอิงได้หลายแบบหากคุณไม่แน่ใจคุณสามารถลองด้วยตัวเอง พลัส.get()จะใช้เมื่อคุณต้องการที่จะเปลี่ยนค่าของวัตถุที่reference_wrapperเป็น holding` คือเป็นสิ่งผิดกฎหมายเพื่อให้คุณได้ใช้งานr=70 r.get()=70ลองเอง !!!!!!
Ankit Acharya

1
ไม่จริง. ก่อนอื่นให้ใช้ถ้อยคำที่ถูกต้อง คุณกำลังแสดง reference_wrapper ถือการอ้างอิงไปยังอาร์เรย์ - ไม่ reference_wrapper ว่าตัวเองมี "การอ้างอิงถึงมากกว่าหนึ่งวัตถุ" Wrapper มีข้อมูลอ้างอิงเดียวเท่านั้น ประการที่สองฉันสามารถรับการอ้างอิงดั้งเดิมของอาร์เรย์ได้ดี - คุณแน่ใจหรือไม่ว่าคุณไม่ได้ลืม (วงเล็บ) รอบ ๆint a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper ไม่ได้พิเศษที่นี่ตั้งแต่พื้นเมืองT& ไม่ทำงาน
underscore_d

1
@AnkitAcharya ใช่ :-) แต่เพื่อความแม่นยำและผลลัพธ์ที่มีประสิทธิภาพนอกจากนั้นตัวมันเองก็หมายถึงวัตถุชิ้นเดียวเท่านั้น อย่างไรก็ตามแน่นอนว่าคุณถูกต้องซึ่งแตกต่างจากการอ้างอิงทั่วไปคือwrapperสามารถไปในคอนเทนเนอร์ได้ สิ่งนี้มีประโยชน์ แต่ฉันคิดว่าผู้คนตีความผิดว่านี่ก้าวหน้ากว่าที่เป็นจริง หากฉันต้องการอาร์เรย์ของ 'refs' ฉันมักจะข้ามคนกลางไปด้วยvector<Item *>ซึ่งเป็นสิ่งที่wrapperเดือดลงไป ... และหวังว่าผู้ต่อต้านตัวชี้จะไม่พบฉัน กรณีการใช้งานที่น่าเชื่อถือนั้นแตกต่างกันและซับซ้อนมากขึ้น
underscore_d

1
"reference_wrapper มีประโยชน์มากเมื่อคุณต้องการเลียนแบบการอ้างอิงของวัตถุที่สามารถคัดลอกได้" แต่นั่นไม่ได้พ่ายแพ้จุดประสงค์ของการอ้างอิงหรือไม่? คุณกำลังอ้างถึงสิ่งที่เกิดขึ้นจริง นั่นคือสิ่งที่อ้างอิงคือ การอ้างอิงที่สามารถคัดลอกได้ดูเหมือนจะไม่มีจุดหมายเพราะหากคุณต้องการคัดลอกคุณไม่ต้องการการอ้างอิงตั้งแต่แรกคุณควรคัดลอกวัตถุต้นฉบับ ดูเหมือนว่าจะมีความซับซ้อนที่ไม่จำเป็นในการแก้ปัญหาซึ่งไม่จำเป็นต้องมีอยู่ตั้งแต่แรก
สตู

53

std::reference_wrapper ได้รับการยอมรับจากสิ่งอำนวยความสะดวกมาตรฐานเพื่อให้สามารถส่งผ่านวัตถุโดยการอ้างอิงในบริบทแบบ pass-by-value

ตัวอย่างเช่นstd::bindสามารถนำไปใช้ในstd::ref()บางสิ่งบางอย่างส่งด้วยค่าและแกะกลับเข้าไปในข้อมูลอ้างอิงในภายหลัง

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

ตัวอย่างข้อมูลนี้แสดงผล:

10
20

ค่าของiได้ถูกเก็บไว้ (นำมาโดยค่า) ในf1จุดที่เริ่มต้น แต่f2ได้เก็บstd::reference_wrapperค่าไว้ตามค่าดังนั้นจึงทำงานเหมือนที่ใช้ในint&ไฟล์.


2
@CppNITR มาแน่! ขอเวลารวบรวมตัวอย่างเล็ก ๆ น้อย ๆ :)
Quentin

1
ความแตกต่างระหว่าง T & & ref (T)
CppNITR

3
@CppNITR std::ref(T)ส่งกลับไฟล์std::reference_wrapper. มันเป็นมากกว่าตัวชี้ที่ห่อหุ้มไว้เล็กน้อย แต่ห้องสมุดจำได้ว่า "เฮ้ฉันควรจะเป็นข้อมูลอ้างอิง! โปรดเปลี่ยนฉันให้กลับมาเป็นหนึ่งเดียวเมื่อคุณเดินผ่านฉันไปแล้ว"
Quentin

38

การอ้างอิง ( T&หรือT&&) เป็นองค์ประกอบพิเศษในภาษา C ++ อนุญาตให้จัดการวัตถุโดยการอ้างอิงและมีกรณีการใช้งานพิเศษในภาษา ตัวอย่างเช่นคุณไม่สามารถสร้างคอนเทนเนอร์มาตรฐานเพื่อเก็บข้อมูลอ้างอิงได้: vector<T&>มีรูปแบบไม่ถูกต้องและสร้างข้อผิดพลาดในการคอมไพล์

std::reference_wrapperบนมืออื่น ๆ ที่เป็นวัตถุ c ++ สามารถที่จะถือเป็นข้อมูลอ้างอิง ดังนั้นคุณสามารถใช้ในภาชนะมาตรฐานได้

std::refเป็นฟังก์ชันมาตรฐานที่ส่งคืนstd::reference_wrapperอาร์กิวเมนต์ ในแนวคิดเดียวกันให้std::crefกลับstd::reference_wrapperไปที่การอ้างอิง const

คุณสมบัติที่น่าสนใจอย่างหนึ่งของ a std::reference_wrapperคือมันมีoperator T& () const noexcept;. นั่นหมายความว่าแม้ว่าจะเป็นวัตถุจริงแต่ก็สามารถแปลงเป็นการอ้างอิงที่ถืออยู่ได้โดยอัตโนมัติ ดังนั้น:

  • เนื่องจากเป็นวัตถุที่กำหนดสำเนาได้จึงสามารถใช้ในคอนเทนเนอร์หรือในกรณีอื่น ๆ ที่ไม่อนุญาตให้มีการอ้างอิง
  • ด้วยเหตุoperator T& () const noexcept;นี้จึงสามารถใช้ที่ใดก็ได้ที่คุณสามารถใช้การอ้างอิงได้เนื่องจากจะถูกแปลงเป็นข้อมูลอ้างอิงโดยอัตโนมัติ

1
โหวตให้คะแนนส่วนใหญ่เนื่องจากการพูดถึงoperator T& ()คำตอบอีก 2 ข้อที่ไม่สามารถพูดถึงได้
metablaster
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.