มันประกาศการอ้างอิง rvalue (เอกสารข้อเสนอมาตรฐาน)
นี่คือข้อมูลเบื้องต้นเกี่ยวกับการอ้างอิงค่า
นี่คือลักษณะที่ยอดเยี่ยมในเชิงลึกที่อ้างอิง rvalue โดยหนึ่งในห้องสมุดมาตรฐานไมโครซอฟท์พัฒนา
ข้อควรระวัง:บทความที่เชื่อมโยงกับ MSDN ("การอ้างอิง Rvalue: C ++ 0x คุณลักษณะใน VC10, ตอนที่ 2") เป็นการแนะนำที่ชัดเจนมากเกี่ยวกับการอ้างอิง Rvalue แต่ทำให้คำสั่งเกี่ยวกับการอ้างอิง Rvalue ที่ครั้งหนึ่งจริงใน C ++ 11 มาตรฐาน แต่ไม่เป็นความจริงสำหรับขั้นตอนสุดท้าย! โดยเฉพาะมันบอกว่าในหลาย ๆ จุดที่การอ้างอิง rvalue สามารถผูกกับ lvalues ซึ่งครั้งหนึ่งเคยเป็นจริง แต่ถูกเปลี่ยน (เช่น int x; int && rrx = x; ไม่รวบรวมใน GCC) - drewbarbs 13 ก.ค. 14 เวลา 16:12
ความแตกต่างที่ใหญ่ที่สุดระหว่างการอ้างอิง C ++ 03 (ปัจจุบันเรียกว่าการอ้างอิง lvalue ใน C ++ 11) คือมันสามารถผูกเข้ากับ rvalue แบบชั่วคราวโดยไม่ต้องเป็น const ดังนั้นไวยากรณ์นี้จึงถูกต้องตามกฎหมาย:
T&& r = T();
การอ้างอิง rvalue เป็นหลักให้สำหรับต่อไปนี้:
ย้ายหมาย ตัวสร้างการย้ายและตัวดำเนินการกำหนดการย้ายในขณะนี้สามารถกำหนดได้โดยใช้การอ้างอิง rvalue แทนการอ้างอิง const-lvalue ปกติ ฟังก์ชั่นย้ายเช่นสำเนายกเว้นมันไม่จำเป็นต้องรักษาแหล่งที่มาไม่เปลี่ยนแปลง ในความเป็นจริงมันมักจะแก้ไขแหล่งที่มาว่ามันไม่ได้เป็นเจ้าของทรัพยากรที่ย้าย นี่เป็นสิ่งที่ยอดเยี่ยมสำหรับการกำจัดสำเนาที่ไม่เกี่ยวข้องโดยเฉพาะอย่างยิ่งในการใช้งานไลบรารีมาตรฐาน
ตัวอย่างเช่นตัวสร้างสำเนาอาจมีลักษณะเช่นนี้:
foo(foo const& other)
{
this->length = other.length;
this->ptr = new int[other.length];
copy(other.ptr, other.ptr + other.length, this->ptr);
}
หากคอนสตรัคเตอร์นี้ผ่านไปชั่วคราวสำเนาจะไม่จำเป็นเพราะเรารู้ว่าจะถูกทำลายชั่วคราว เหตุใดจึงไม่ใช้ทรัพยากรที่จัดสรรไว้ชั่วคราวแล้ว ใน C ++ 03 ไม่มีทางที่จะป้องกันการคัดลอกเนื่องจากเราไม่สามารถระบุได้ว่าเราถูกส่งผ่านชั่วคราว ใน C ++ 11 เราสามารถโหลดตัวสร้างการย้ายมากเกินไป:
foo(foo&& other)
{
this->length = other.length;
this->ptr = other.ptr;
other.length = 0;
other.ptr = nullptr;
}
สังเกตเห็นความแตกต่างใหญ่ที่นี่: ตัวสร้างการย้ายปรับเปลี่ยนอาร์กิวเมนต์ของจริง สิ่งนี้จะมีประสิทธิภาพ "ย้าย" ชั่วคราวลงในวัตถุที่กำลังสร้างจึงกำจัดสำเนาที่ไม่จำเป็น
ตัวสร้างการย้ายจะถูกใช้สำหรับชั่วขณะและสำหรับการอ้างอิงไม่ใช่ lvalue แบบ const ที่แปลงอย่างชัดเจนเป็นการอ้างอิงแบบ rvalue โดยใช้std::move
ฟังก์ชัน (เพิ่งทำการแปลง) รหัสต่อไปนี้ทั้งสองเรียกตัวสร้างการย้ายสำหรับf1
และf2
:
foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"
การส่งต่อที่สมบูรณ์แบบ การอ้างอิงค่า rvalue ทำให้เราสามารถส่งต่อข้อโต้แย้งอย่างเหมาะสมสำหรับฟังก์ชั่น templated ยกตัวอย่างฟังก์ชั่นโรงงานนี้:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
return std::unique_ptr<T>(new T(a1));
}
ถ้าเราเรียกว่าfactory<foo>(5)
อาร์กิวเมนต์จะอนุมานได้ว่าเป็นที่int&
ซึ่งจะไม่ผูกกับตัวอักษร 5 แม้ว่าfoo
's int
คอนสตรัคใช้เวลา ทีนี้เราสามารถใช้แทนได้A1 const&
แต่ถ้าfoo
ใช้อาร์กิวเมนต์ตัวสร้างโดยการอ้างอิงที่ไม่ใช่ const? ที่จะทำให้การทำงานของโรงงานทั่วไปอย่างแท้จริงเราจะต้องเกินโรงงานและA1&
A1 const&
อาจเป็นเรื่องปกติหากโรงงานใช้พารามิเตอร์ 1 ชนิด แต่พารามิเตอร์เพิ่มเติมแต่ละประเภทจะทวีคูณการโอเวอร์โหลดที่จำเป็นตาม 2 ซึ่งไม่สามารถทำได้อย่างรวดเร็ว
การอ้างอิง rvalue แก้ไขปัญหานี้โดยอนุญาตให้ไลบรารีมาตรฐานกำหนดstd::forward
ฟังก์ชันที่สามารถส่งต่อการอ้างอิง lvalue / rvalue ได้อย่างถูกต้อง สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการstd::forward
ทำงานให้ดูคำตอบที่ยอดเยี่ยมนี้
สิ่งนี้ทำให้เราสามารถกำหนดฟังก์ชั่นจากโรงงานดังนี้:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}
ตอนนี้อาร์กิวเมนต์ rvalue / lvalue-ness ของอาร์กิวเมนต์จะถูกรักษาไว้เมื่อส่งผ่านไปยังตัวT
สร้างของ นั่นหมายความว่าถ้าโรงงานถูกเรียกด้วยค่า rvalue ตัวT
สร้างของจะถูกเรียกด้วยค่า rvalue ถ้าโรงงานถูกเรียกด้วย lvalue คอนสตรัคเตอร์T
จะถูกเรียกด้วย lvalue ฟังก์ชั่นที่ได้รับการปรับปรุงจากโรงงานทำงานได้เนื่องจากกฎพิเศษหนึ่งข้อ:
เมื่อประเภทพารามิเตอร์ฟังก์ชั่นเป็นของรูปแบบT&&
ที่T
เป็นพารามิเตอร์แม่แบบและฟังก์ชั่นการโต้แย้งเป็น lvalue ประเภทA
ประเภทที่A&
ใช้สำหรับการหักอาร์กิวเมนต์แม่แบบ
ดังนั้นเราสามารถใช้โรงงานเช่นนี้:
auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1); // calls foo(foo const&)
คุณสมบัติอ้างอิง rvalue ที่สำคัญ :
- สำหรับการแก้ปัญหาเกินlvalues ชอบผูกพันกับการอ้างอิง lvalue และ rvalues ชอบผูกพันกับการอ้างอิง ดังนั้นทำไม Tem Tem ต้องการเรียกตัวสร้างการย้าย / ผู้ประกอบการที่ได้รับมอบหมายย้ายมากกว่าผู้ประกอบการคัดลอก / ผู้ประกอบการได้รับมอบหมาย
- อ้างอิง rvalue โดยปริยายจะผูกกับ rvalues และชั่วคราวที่มีผลมาจากการแปลงนัย นั่น
float f = 0f; int&& i = f;
คือการก่อตัวที่ดีเพราะเป็นปริยายแปลงลอยเพราะ int; การอ้างอิงจะเป็นการชั่วคราวที่เป็นผลลัพธ์ของการแปลง
- การอ้างอิง rvalue ที่ระบุชื่อคือ lvalues การอ้างอิงค่า rvalue ที่ไม่มีชื่อนั้นเป็น rvalues นี่เป็นสิ่งสำคัญที่จะต้องเข้าใจว่าเหตุใดการ
std::move
โทรจึงมีความจำเป็นใน:foo&& r = foo(); foo f = std::move(r);