การอ้างอิง rvalue กับ const มีประโยชน์หรือไม่?


91

ฉันเดาว่าไม่ แต่ฉันขอยืนยัน จะมีการใช้งานใด ๆconst Foo&&ที่Fooเป็นชนิดชั้น?


2
ในวิดีโอนี้ STL กล่าวว่าconst&&มีความสำคัญมากแม้ว่าเขาจะไม่ได้บอกว่าทำไม: youtube.com/watch?v=JhgWFYfdIho#t=54m20s
Aaron McDaid

คำตอบ:


80

มีประโยชน์เป็นครั้งคราว C ++ 0x แบบร่างนั้นใช้ในบางที่ตัวอย่างเช่น:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

การโอเวอร์โหลดสองครั้งข้างต้นทำให้มั่นใจได้ว่าฟังก์ชันอื่น ๆref(T&)และcref(const T&)ฟังก์ชันไม่ผูกกับค่า rvalues ​​(ซึ่งอาจเป็นไปได้)

อัปเดต

ฉันเพิ่งตรวจสอบมาตรฐานอย่างเป็นทางการN3290ซึ่งน่าเสียดายที่ไม่มีให้ใช้งานแบบสาธารณะและมีอยู่ในวัตถุฟังก์ชัน 20.8 [function.objects] / p2:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

จากนั้นฉันตรวจสอบร่างโพสต์ C ++ 11 ล่าสุดซึ่งเผยแพร่ต่อสาธารณะN3485และใน 20.8 วัตถุฟังก์ชัน [function.objects] / p2 มันยังคงระบุว่า:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

เมื่อดูที่cppreferenceปรากฏว่านี่ไม่ใช่กรณีอีกต่อไป ความคิดใด ๆ ทำไม? const T&&มีการใช้สถานที่อื่น ๆหรือไม่?
Pubby

60
เหตุใดคุณจึงใส่รหัสเดียวกันในคำตอบของคุณสามครั้ง ฉันพยายามค้นหาความแตกต่างมานานเกินไป
typ1232

9
@ typ1232: ดูเหมือนว่าฉันได้อัปเดตคำตอบเกือบ 2 ปีหลังจากที่ฉันตอบเนื่องจากข้อกังวลในความคิดเห็นที่ว่าฟังก์ชันที่อ้างอิงไม่ปรากฏอีกต่อไป ฉันคัดลอก / วางจาก N3290 และจากร่างล่าสุด N3485 เพื่อแสดงว่าฟังก์ชันยังคงปรากฏอยู่ การใช้การคัดลอก / วางในความคิดของฉันในเวลานั้นเป็นวิธีที่ดีที่สุดที่จะทำให้แน่ใจว่ามีสายตามากกว่าที่ฉันจะยืนยันได้ว่าฉันไม่ได้มองข้ามการเปลี่ยนแปลงเล็กน้อยในลายเซ็นเหล่านี้
Howard Hinnant

1
@kevinarpe: "overloads อื่น ๆ " (ไม่แสดงที่นี่ แต่อยู่ในมาตรฐาน) ใช้การอ้างอิงค่า lvalue และจะไม่ถูกลบ การโอเวอร์โหลดที่แสดงที่นี่เป็นการจับคู่ที่ดีกว่าสำหรับ rvalues ​​มากกว่าโอเวอร์โหลดที่ใช้การอ้างอิง lvalue ดังนั้นอาร์กิวเมนต์ rvalue จะเชื่อมโยงกับโอเวอร์โหลดที่แสดงที่นี่และจากนั้นทำให้เกิดข้อผิดพลาดเวลาคอมไพล์เนื่องจากโอเวอร์โหลดเหล่านี้ถูกลบ
Howard Hinnant

1
การใช้const T&&ป้องกันไม่ให้ใครสักคนโง่ใช้ args ref<const A&>(...)แม่แบบที่ชัดเจนของรูปแบบ นั่นไม่ใช่ข้อโต้แย้งที่หนักหนาสาหัสนัก แต่ค่าใช้จ่ายที่const T&&สูงเกินไปT&&นั้นค่อนข้างน้อย
Howard Hinnant

7

ความหมายของการได้รับการอ้างอิง const rvalue (ไม่ใช่สำหรับ=delete) มีไว้สำหรับพูดว่า:

  • เราไม่สนับสนุนการดำเนินการสำหรับ lvalues!
  • แม้ว่าเราจะยังคงคัดลอกเนื่องจากเราไม่สามารถย้ายทรัพยากรที่ส่งผ่านไปได้หรือเนื่องจากไม่มีความหมายที่แท้จริงสำหรับการ "ย้าย"

กรณีการใช้งานต่อไปนี้อาจเป็น IMHO กรณีการใช้งานที่ดีสำหรับการอ้างอิง rvalue ไปยัง constแม้ว่าภาษาจะตัดสินใจที่จะไม่ใช้แนวทางนี้ (ดูโพสต์ SO ต้นฉบับ )


กรณี: ตัวสร้างตัวชี้อัจฉริยะจากตัวชี้ดิบ

โดยปกติจะแนะนำให้ใช้make_uniqueและmake_sharedแต่ทั้งสองอย่างunique_ptrและshared_ptrสามารถสร้างได้จากตัวชี้ดิบ ตัวสร้างทั้งสองรับตัวชี้ตามค่าและคัดลอก ทั้งสองอนุญาต (กล่าวคือในแง่ของ: อย่าป้องกัน ) การใช้งานต่อเนื่องของตัวชี้เดิมที่ส่งผ่านไปยังพวกเขาในตัวสร้าง

รหัสต่อไปนี้รวบรวมและผลลัพธ์พร้อมฟรีสองเท่า :

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

ทั้งสองอย่างunique_ptrและshared_ptrสามารถป้องกันข้างต้นได้หากตัวสร้างที่เกี่ยวข้องคาดว่าจะได้ตัวชี้ดิบเป็นค่า const rเช่นสำหรับunique_ptr:

unique_ptr(T* const&& p) : ptr{p} {}

ในกรณีนี้โค้ดฟรีสองเท่าด้านบนจะไม่รวบรวม แต่สิ่งต่อไปนี้จะ:

std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) };     // ok, rvalue

โปรดทราบว่าptrยังสามารถใช้ได้หลังจากย้ายไปแล้วดังนั้นข้อบกพร่องที่อาจเกิดขึ้นจะไม่หายไปทั้งหมด แต่ถ้าผู้ใช้จำเป็นต้องเรียกstd::moveบั๊กดังกล่าวจะตกอยู่ในกฎทั่วไปของ: อย่าใช้ทรัพยากรที่ถูกย้าย


ใครสามารถถาม: ตกลง แต่ทำไมT* const&& p ?

เหตุผลง่ายๆที่จะช่วยให้การสร้างจากตัวชี้unique_ptr const โปรดจำไว้ว่าการอ้างอิง const rvalueเป็นทั่วไปมากขึ้นกว่าเพียงแค่การอ้างอิง rvalueขณะที่มันยอมรับทั้งในและconst non-constดังนั้นเราสามารถอนุญาตสิ่งต่อไปนี้:

int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };

สิ่งนี้จะไม่เกิดขึ้นหากเราคาดหวังเพียงการอ้างอิง rvalue (ข้อผิดพลาดในการคอมไพล์: ไม่สามารถผูกconst rvalueกับrvalue ได้ )


อย่างไรก็ตามนี่ก็สายเกินไปที่จะเสนอสิ่งนี้ แต่แนวคิดนี้นำเสนอการใช้การอ้างอิง rvalue กับ constอย่างสมเหตุสมผลconst


ฉันอยู่ท่ามกลางคนที่มีความคิดคล้าย ๆ กัน แต่ฉันไม่มีกำลังใจที่จะผลักดันไปข้างหน้า
Red.Wave

"auto p = std :: unique_ptr {std :: move (ptr)};" ไม่รวบรวมข้อผิดพลาด "การหักอาร์กิวเมนต์เทมเพลตคลาสล้มเหลว" ฉันคิดว่ามันควรจะเป็น "unique_ptr <int>"
Zehui Lin

4

พวกเขาได้รับอนุญาตและแม้กระทั่งฟังก์ชันที่ได้รับการจัดอันดับตามconstแต่เนื่องจากคุณไม่สามารถย้ายจากวัตถุ const ที่อ้างถึงconst Foo&&ได้จึงไม่มีประโยชน์


คำพูดที่ "จัดอันดับ" หมายความว่าอย่างไร ฉันเดาว่าจะทำอะไรกับความละเอียดเกินพิกัด?
fredoverflow

เหตุใดคุณจึงไม่สามารถย้ายจาก const rvalue-ref ได้หากประเภทที่กำหนดมี move ctor ที่รับ const rvalue-ref
Fred Nurk

6
@FredOverflow อันดับของการโอเวอร์โหลดคือ:const T&, T&, const T&&, T&&
Gene Bushuyev

2
@ เฟรด: คุณจะย้ายโดยไม่แก้ไขแหล่งที่มาได้อย่างไร?
fredoverflow

3
@ เฟรด: สมาชิกข้อมูลที่เปลี่ยนแปลงได้หรืออาจจะย้ายสำหรับประเภทสมมุติฐานนี้ไม่จำเป็นต้องแก้ไขสมาชิกข้อมูล
Fred Nurk

3

นอกจากstd :: refแล้วไลบรารีมาตรฐานยังใช้การอ้างอิง const rvalue ในstd :: as_constเพื่อวัตถุประสงค์เดียวกัน

template <class T>
void as_const(const T&&) = delete;

นอกจากนี้ยังใช้เป็นค่าส่งคืนในstd :: ทางเลือกเมื่อรับค่าที่รวม:

constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;

เช่นเดียวกับในstd :: get :

template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;

นี่เป็นที่น่าจะเป็นไปได้เพื่อรักษาหมวดหมู่ของค่าตลอดจนความมั่นคงของ wrapper เมื่อเข้าถึงค่าที่รวมไว้

สิ่งนี้สร้างความแตกต่างว่าสามารถเรียกฟังก์ชันที่มีคุณสมบัติการอ้างอิง const rvalue บนวัตถุที่ห่อหุ้มได้หรือไม่ ที่กล่าวว่าฉันไม่ทราบการใช้งานใด ๆ สำหรับฟังก์ชันที่ผ่านการรับรอง const rvalue ref


1

ฉันไม่สามารถนึกถึงสถานการณ์ที่สิ่งนี้จะเป็นประโยชน์โดยตรง แต่อาจใช้โดยอ้อม:

template<class T>
void f(T const &x) {
  cout << "lvalue";
}
template<class T>
void f(T &&x) {
  cout << "rvalue";
}

template<class T>
void g(T &x) {
  f(T());
}

template<class T>
void h(T const &x) {
  g(x);
}

T ในgคือ T const ดังนั้นf 's x จึงเป็น T const &&

เป็นไปได้ว่าสิ่งนี้จะทำให้เกิดข้อผิดพลาด comile ในf (เมื่อพยายามย้ายหรือใช้วัตถุ) แต่fอาจใช้ rvalue-ref เพื่อที่จะไม่สามารถเรียกใช้ lvalues ​​ได้โดยไม่ต้องแก้ไข rvalue (เช่นเดียวกับที่ง่ายเกินไป ตัวอย่างด้านบน)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.