rvalues, lvalues, xvalues, glvalues ​​และ prvalues ​​คืออะไร?


1356

ใน C ++ 03, การแสดงออกเป็นทั้งrvalueหรือlvalue

ใน C ++ 11 นิพจน์สามารถเป็น:

  1. rvalue
  2. lvalue
  3. Xvalue
  4. glvalue
  5. prvalue

สองประเภทได้กลายเป็นห้าประเภท

  • นิพจน์ประเภทใหม่เหล่านี้มีอะไรบ้าง
  • หมวดหมู่ใหม่เหล่านี้เกี่ยวข้องกับหมวด rvalue และ lvalue ที่มีอยู่ได้อย่างไร
  • หมวดหมู่ rvalue และ lvalue ใน C ++ 0x เหมือนกับที่อยู่ใน C ++ 03 หรือไม่?
  • เหตุใดจึงต้องมีหมวดหมู่ใหม่เหล่านี้ เป็นWG21เทพแค่พยายามที่จะสร้างความสับสนให้เราปุถุชนเพียง?

9
@Philip Potter: ใน C ++ 03 ใช่. lvalue สามารถใช้เป็น rvalue ได้เนื่องจากมีการแปลง lvalue-to-rvalue มาตรฐาน
James McNellis

14
@Tyler: "ถ้าคุณสามารถกำหนดให้มันเป็น lvalue มิฉะนั้นจะ rvalue" -> ผิดคุณสามารถกำหนดให้ rvalues string("hello") = string("world")ระดับ:
fredoverflow

4
โปรดทราบว่านี่เป็นหมวดหมู่ค่า มีคุณสมบัติเพิ่มเติมที่นิพจน์สามารถมีได้ ซึ่งรวมถึงบิตฟิลด์ (จริง / เท็จ) ชั่วคราว (จริง / เท็จ) และประเภท (ประเภทของมัน)
Johannes Schaub - litb

30
ฉันคิดว่าลิงก์ของ Fred ด้านบนดีกว่าคำตอบใด ๆ ที่นี่ แม้ว่าลิงก์จะตายไป มันถูกย้ายไปที่: stroustrup.com/terminology.pdf
R. Martinho Fernandes

74
ใน C ++ แม้กระทั่งประเภทของคุณก็มีประเภท
nielsbot

คำตอบ:


634

ฉันเดาว่าเอกสารนี้อาจใช้เป็นการแนะนำสั้น ๆ : n3055

การสังหารหมู่ทั้งหมดเริ่มต้นด้วยความหมายของการเคลื่อนไหว เมื่อเรามีนิพจน์ที่สามารถเคลื่อนย้ายและไม่คัดลอกได้ทันใดนั้นก็ง่ายที่จะเข้าใจกฎเรียกร้องความแตกต่างระหว่างการแสดงออกที่สามารถเคลื่อนย้ายและในทิศทางที่

จากสิ่งที่ฉันเดาตามร่างความแตกต่างของค่า r / l ยังคงเหมือนเดิมเฉพาะในบริบทของสิ่งที่เคลื่อนไหวได้ยุ่ง

พวกเขาต้องการ? อาจไม่ได้ถ้าเราต้องการที่จะสูญเสียคุณสมบัติใหม่ แต่เพื่อให้การเพิ่มประสิทธิภาพดีขึ้นเราอาจต้องยอมรับพวกเขา

Quoting n3055 :

  • lvalue (ที่เรียกว่าในอดีตเพราะ lvalues อาจปรากฏอยู่ทางด้านซ้ายมือของการแสดงออกที่ได้รับมอบหมาย) กำหนดฟังก์ชั่นหรือวัตถุ [ตัวอย่าง: ถ้าEเป็นการแสดงออกของประเภทพอยน์เตอร์แล้ว*E นิพจน์ lvalue หมายถึงวัตถุหรือฟังก์ชั่นซึ่งเป็นE จุด เป็นอีกตัวอย่างหนึ่งผลลัพธ์ของการเรียกใช้ฟังก์ชันที่มีชนิดส่งคืนคือการอ้างอิง lvalue คือ lvalue]
  • ค่าxvalue (ค่า“ eXpiring”) ยังหมายถึงวัตถุซึ่งมักจะใกล้ถึงจุดสิ้นสุดของอายุการใช้งาน (ตัวอย่างเช่นทรัพยากรอาจถูกเคลื่อนย้าย) xvalue คือผลลัพธ์ของนิพจน์บางประเภทที่เกี่ยวข้องกับการอ้างอิง rvalue [ตัวอย่าง: ผลลัพธ์ของการเรียกใช้ฟังก์ชันที่มีชนิดส่งคืนคือการอ้างอิง rvalue คือ xvalue]
  • glvalue (“ทั่วไป” lvalue) เป็นlvalue หรือXvalue
  • rvalue (ที่เรียกว่าในอดีตเพราะ rvalues อาจปรากฏอยู่ทางด้านขวามือของการแสดงออกที่ได้รับมอบหมาย) เป็น Xvalue วัตถุชั่วคราวหรือ subobject ดังกล่าวหรือมูลค่าที่ไม่ได้เกี่ยวข้องกับวัตถุ
  • prvalue (“บริสุทธิ์” rvalue) เป็น rvalue ที่ไม่ได้เป็น Xvalue [ตัวอย่าง: ผลลัพธ์ของการเรียกฟังก์ชั่นที่มีประเภทส่งคืนไม่ใช่การอ้างอิงคือ prvalue]

เอกสารที่เป็นปัญหาเป็นข้อมูลอ้างอิงที่ดีสำหรับคำถามนี้เพราะมันแสดงให้เห็นถึงการเปลี่ยนแปลงที่แน่นอนในมาตรฐานที่เกิดขึ้นอันเป็นผลมาจากการเปิดตัวระบบการตั้งชื่อใหม่


ขอบคุณคำตอบนี้มีประโยชน์จริง ๆ ! แต่คอมไพเลอร์ของฉันไม่เห็นด้วยกับตัวอย่างของคุณสำหรับ xvalues ​​และ prvalues; พวกเขาเป็นสิ่งที่ตรงกันข้าม การส่งคืนโดยการอ้างอิง rvalue ทำให้ฉันมีค่ามากและการส่งกลับโดยค่าจะให้ xvalue แก่ฉัน คุณทำให้พวกเขาปะปนกันหรือเตียงทดสอบของฉันแตกหรือเปล่า ฉันลองสิ่งนี้กับ GCC 4.6.1 เสียงดังกราว (จาก svn) และ MSVC และพวกเขาทั้งหมดแสดงพฤติกรรมเดียวกัน
Kim Gräsman

โอ๊ะฉันเพิ่งไปตามลิงก์และสังเกตว่าตัวอย่างอยู่ในแหล่งที่มา ฉันจะไปพบสำเนาของฉันของมาตรฐานและตรวจสอบสิ่งที่มันพูดว่า ...
คิมGräsman

4
ฉันใช้มาโครจากที่นี่เพื่อทดสอบนิพจน์ต่างๆ: stackoverflow.com/a/6114546/96963 อาจเป็นเพราะพวกเขาวินิจฉัยผิดพลาด
Kim Gräsman

1
การเพิ่ม xvalue ไม่ได้มีไว้สำหรับซีแมนทิกส์การย้าย เฉพาะกับทั้ง lvalue และ rvalue ความหมายของการย้ายการส่งต่อที่สมบูรณ์แบบและการอ้างอิง rvalue ยังคงใช้ได้ดี ฉันคิดว่า xvalue นั้นมีไว้สำหรับตัวดำเนินการ dentype เท่านั้น: ถ้านิพจน์ตัวถูกดำเนินการคือ xvalue, decltype ให้ประเภทของการอ้างอิงค่า rvalue
แกนด์

1
@MuhamedCicak "ทุกการแสดงออกเป็น lvalue หรือ rvalue": มันเป็นความจริง และมาตรฐาน (หรือเอกสาร n3055) ไม่ได้บอกว่ามันผิด เหตุผลที่ประโยคนี้มีการขีดฆ่าคือคุณกำลังดูการเปลี่ยนแปลงระหว่างเอกสารสองเวอร์ชัน ประโยคดังกล่าวถูกลบออกเพราะไม่จำเป็นหลังจากเพิ่มคำอธิบายที่แม่นยำยิ่งขึ้น
สูงสุด

337

นิพจน์ประเภทใหม่เหล่านี้มีอะไรบ้าง

FCD (n3092)มีคำอธิบายที่ดีเยี่ยม:

- lvalue (เรียกอีกอย่างว่าในอดีตเนื่องจาก lvalues ​​อาจปรากฏที่ด้านซ้ายมือของนิพจน์การมอบหมาย) จะกำหนดฟังก์ชันหรือวัตถุ [ตัวอย่าง: ถ้า E เป็นนิพจน์ประเภทตัวชี้ดังนั้น * E คือนิพจน์ lvalue ที่อ้างถึงวัตถุหรือฟังก์ชันที่จุด E นั้น เป็นอีกตัวอย่างหนึ่งผลลัพธ์ของการเรียกใช้ฟังก์ชันที่มีชนิดส่งคืนคือการอ้างอิง lvalue คือ lvalue - ตัวอย่าง]

- ค่า xvalue (ค่า“ eXpiring”) ยังอ้างถึงวัตถุซึ่งโดยปกติจะใกล้ถึงจุดสิ้นสุดของอายุการใช้งาน (เพื่อให้ทรัพยากรสามารถเคลื่อนย้ายได้ตัวอย่างเช่น) xvalue คือผลลัพธ์ของนิพจน์บางประเภทที่เกี่ยวข้องกับการอ้างอิง rvalue (8.3.2) [ตัวอย่าง: ผลลัพธ์ของการเรียกใช้ฟังก์ชันที่มีชนิดส่งคืนคือการอ้างอิง rvalue คือ xvalue - ตัวอย่าง]

- glvalue (“ generalized” lvalue) คือ lvalue หรือ xvalue

- ค่า rvalue (เรียกว่าในอดีตเนื่องจากค่า rvalues ​​อาจปรากฏที่ด้านขวามือของนิพจน์การกำหนดค่า) คือ xvalue วัตถุชั่วคราว (12.2) หรือ subobject หรือค่าที่ไม่เกี่ยวข้องกับวัตถุ

- prvalue (“ pure” rvalue) คือ rvalue ที่ไม่ใช่ xvalue [ตัวอย่าง: ผลลัพธ์ของการเรียกฟังก์ชั่นที่มีประเภทส่งคืนไม่ใช่การอ้างอิงคือ prvalue ค่าของตัวอักษรเช่น 12, 7.3e5 หรือความจริงก็เป็นที่แพร่หลาย - ตัวอย่าง]

ทุกการแสดงออกเป็นของหนึ่งในการจำแนกประเภทพื้นฐานในอนุกรมวิธานนี้: lvalue, xvalue หรือ prvalue คุณสมบัติของนิพจน์นี้เรียกว่าหมวดหมู่ค่า [หมายเหตุ: การอภิปรายของตัวดำเนินการในตัวในข้อ 5 ระบุประเภทของค่าที่ให้ผลและหมวดค่าของตัวถูกดำเนินการที่คาดหวัง ตัวอย่างเช่นตัวดำเนินการกำหนดค่าในตัวคาดหวังว่าตัวถูกดำเนินการด้านซ้ายเป็น lvalue และตัวถูกดำเนินการด้านขวาคือ prvalue และให้ค่า lvalue เป็นผลลัพธ์ โอเปอเรเตอร์ที่ผู้ใช้กำหนดคือฟังก์ชั่นและหมวดหมู่ของค่าที่พวกเขาคาดหวังและผลตอบแทนจะถูกกำหนดโดยพารามิเตอร์และประเภทผลตอบแทน - บันทึกย่อ

ผมขอแนะนำให้คุณอ่านส่วนทั้งหมด3.10 lvalues และ rvaluesแม้ว่า

หมวดหมู่ใหม่เหล่านี้เกี่ยวข้องกับหมวด rvalue และ lvalue ที่มีอยู่ได้อย่างไร

อีกครั้ง:

อนุกรมวิธาน

หมวดหมู่ rvalue และ lvalue ใน C ++ 0x เหมือนกับที่อยู่ใน C ++ 03 หรือไม่?

ซีแมนทิกส์ของค่า rvalues ​​มีวิวัฒนาการโดยเฉพาะอย่างยิ่งกับการแนะนำของซีแมนทิกส์การย้าย

เหตุใดจึงต้องมีหมวดหมู่ใหม่เหล่านี้

เพื่อให้การก่อสร้างการเคลื่อนย้าย / การมอบหมายสามารถกำหนดและสนับสนุนได้


54
ฉันชอบแผนผังที่นี่ ฉันคิดว่าอาจเป็นประโยชน์ในการเริ่มต้นคำตอบด้วย"ทุกการแสดงออกเป็นของการจำแนกประเภทพื้นฐานอย่างใดอย่างหนึ่งในอนุกรมวิธานนี้: lvalue, xvalue หรือ prvalue" จากนั้นมันเป็นเรื่องง่ายที่จะใช้แผนภาพเพื่อแสดงคลาสพื้นฐานทั้งสามที่รวมกันเพื่อสร้างค่าและหาค่า
Aaron McDaid

2
"is glvalue" เทียบเท่ากับ "is prvalue" และ "is rvalue" เทียบเท่ากับ "is not lvalue"
Vladimir Reshetnikov

2
อันนี้ช่วยฉันได้มากที่สุด: bajamircea.github.io/assets/2016-04-07-move-forward/ … (แผนภาพเวนน์ของหมวดค่า)
John P

1
@AaronMcDaid สวัสดีคำถามอย่างรวดเร็วถ้าคุณ / คนที่สามารถตอบ ... ทำไมไม่ตั้งชื่อglvalueเป็นlvalueและlvalueเป็นplvalueเพื่อให้สอดคล้อง?
Vijay Chavda

184

ฉันจะเริ่มต้นด้วยคำถามสุดท้ายของคุณ:

เหตุใดจึงต้องมีหมวดหมู่ใหม่เหล่านี้

มาตรฐาน C ++ มีกฎมากมายที่จัดการกับหมวดหมู่ค่าของนิพจน์ กฎบางข้อสร้างความแตกต่างระหว่าง lvalue และ rvalue ตัวอย่างเช่นเมื่อมันมาถึงความละเอียดเกินพิกัด กฎอื่น ๆ สร้างความแตกต่างระหว่าง glvalue และ prvalue ตัวอย่างเช่นคุณสามารถมีค่าความแปรปรวนที่มีชนิดไม่สมบูรณ์หรือนามธรรม แต่ไม่มีความคุ้มค่าที่มีประเภทที่ไม่สมบูรณ์หรือเป็นนามธรรม ก่อนที่เราจะมีคำศัพท์นี้กฎที่จริงต้องแยกแยะระหว่าง glvalue / prvalue ที่อ้างถึง lvalue / rvalue และพวกมันผิดโดยไม่ได้ตั้งใจหรือมีจำนวนมากอธิบายและยกเว้นกฎ a la "... เว้นแต่ว่า rvalue เป็นเพราะไม่มีชื่อ การอ้างอิงค่า rvalue ... " ดังนั้นจึงเป็นความคิดที่ดีที่จะให้แนวคิดเกี่ยวกับ glvalues ​​และ prvalues ​​ชื่อของตนเอง

นิพจน์ประเภทใหม่เหล่านี้มีอะไรบ้าง หมวดหมู่ใหม่เหล่านี้เกี่ยวข้องกับหมวด rvalue และ lvalue ที่มีอยู่ได้อย่างไร

เรายังมีข้อกำหนด lvalue และ rvalue ที่เข้ากันได้กับ C ++ 98 เราเพิ่งแบ่งค่าออกเป็นสองกลุ่มย่อยคือ xvalues ​​และ prvalues ​​และเราอ้างถึง lvalues ​​และ xvalues ​​เป็น glvalues Xvalues ​​เป็นประเภทค่าใหม่สำหรับการอ้างอิงค่า rvalue ที่ไม่มีชื่อ ทุกการแสดงออกเป็นหนึ่งในสามอย่างนี้: lvalue, xvalue, prvalue แผนภาพ Venn จะมีลักษณะเช่นนี้:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r

ตัวอย่างที่มีฟังก์ชั่น:

int   prvalue();
int&  lvalue();
int&& xvalue();

แต่อย่าลืมว่าการอ้างอิงชื่อ rvalue นั้นเป็น lvalues:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}

165

เหตุใดจึงต้องมีหมวดหมู่ใหม่เหล่านี้ WG21 เหล่าเทพกำลังพยายามสร้างความสับสนให้เราแค่มนุษย์ปุถุชนหรือไม่?

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

เจ้าจะย้ายเฉพาะเมื่อปลอดภัยแน่นอนเท่านั้น

นั่นคือเหตุผลที่หมวดหมู่เหล่านี้มีอยู่: เพื่อให้สามารถพูดคุยเกี่ยวกับค่านิยมที่จะปลอดภัยจากพวกเขาและพูดคุยเกี่ยวกับค่านิยมที่ไม่ปลอดภัย

ในเวอร์ชันแรกสุดของการอ้างอิง r-value การเคลื่อนไหวเกิดขึ้นได้ง่าย ง่ายเกินไป ง่ายพอที่มีโอกาสมากมายสำหรับการย้ายสิ่งต่าง ๆ โดยปริยายเมื่อผู้ใช้ไม่ได้มีความหมายจริงๆ

นี่คือสถานการณ์ที่ปลอดภัยสำหรับการเคลื่อนย้ายบางสิ่ง:

  1. เมื่อมันเป็นการชั่วคราวหรือ subobject ของมัน (prvalue)
  2. เมื่อผู้ใช้มีการกล่าวอย่างชัดเจนที่จะย้าย

หากคุณทำสิ่งนี้:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};

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

มีปัญหาเดียวกับสิ่งนี้; คุณไม่ได้ขอให้ย้าย โอ้คุณอาจจะบอกว่าสิ่งที่&&ควรจะเป็นเบาะแส แต่นั่นไม่ได้เปลี่ยนความจริงที่ว่ามันทำลายกฎ valไม่ใช่ชั่วคราวเพราะชั่วขณะไม่มีชื่อ คุณอาจจะมีการขยายอายุการใช้งานของชั่วคราว แต่นั่นหมายความว่ามันไม่ได้ชั่วคราว ; มันก็เหมือนกับตัวแปรสแต็คอื่น ๆ

หากไม่ใช่ชั่วคราวและคุณไม่ได้ขอให้ย้ายมันแสดงว่าการย้ายผิด

ทางออกที่ชัดเจนคือการทำให้vallvalue ซึ่งหมายความว่าคุณไม่สามารถย้ายจากมัน ตกลงไม่เป็นไร; มันมีชื่อดังนั้นจึงเป็น lvalue

เมื่อคุณทำเช่นนั้นคุณจะไม่สามารถพูดได้อีกต่อไปว่าSomeType&&หมายถึงสิ่งเดียวกันทุกที่ ตอนนี้คุณได้สร้างความแตกต่างระหว่างการอ้างอิงค่า rvalue ที่มีชื่อและการอ้างอิงค่า rvalue ที่ไม่มีชื่อ การอ้างอิง rvalue ที่มีชื่อคือ lvalues นั่นคือทางออกของเราด้านบน ดังนั้นสิ่งที่เราเรียกการอ้างอิงค่า rvalue ที่ไม่มีชื่อ (ค่าส่งคืนจากFuncด้านบน)

ไม่ใช่ lvalue เพราะคุณไม่สามารถย้ายจาก lvalue ได้ และเราต้องสามารถเคลื่อนย้ายได้ด้วยการคืน a &&; คุณสามารถพูดอย่างอื่นเพื่อย้ายบางสิ่งได้อย่างชัดเจน? นั่นคือสิ่งที่std::moveจะกลับมาหลังจากทั้งหมด ไม่ใช่ rvalue (แบบเก่า) เพราะอาจอยู่ทางซ้ายของสมการ (จริงๆแล้วสิ่งต่าง ๆ มีความซับซ้อนกว่าเล็กน้อยดูคำถามนี้และความคิดเห็นด้านล่าง) มันไม่ใช่ค่า lvalue หรือ rvalue; มันเป็นสิ่งใหม่

สิ่งที่เรามีคือค่าที่คุณสามารถถือได้ว่าเป็น lvalue ยกเว้นว่ามันเคลื่อนย้ายได้โดยปริยาย เราเรียกมันว่าค่า xvalue

โปรดทราบว่าค่า xvalues ​​คือสิ่งที่ทำให้เราได้รับค่าสองประเภทอื่น ๆ :

  • prvalue เป็นเพียงชื่อใหม่สำหรับ rvalue ประเภทก่อนหน้านั่นคือค่าเหล่านั้นไม่ใช่ค่า xvalue

  • Glvalues ​​คือการรวมกันของ xvalues ​​และ lvalues ​​ในกลุ่มเดียวเพราะพวกเขาแบ่งปันคุณสมบัติมากมายร่วมกัน

ดังนั้นจริงๆมันทั้งหมดลงมาถึงค่า xvalues ​​และความจำเป็นในการ จำกัด การเคลื่อนไหวไปยังสถานที่บางแห่งเท่านั้น สถานที่เหล่านั้นถูกกำหนดโดยประเภท rvalue; prvalues ​​คือการย้ายโดยนัยและ xvalues ​​เป็นการย้ายที่ชัดเจน ( std::moveส่งคืน xvalue)


11
@ โทมัส: มันเป็นตัวอย่าง; ไม่สำคัญว่าจะสร้างมูลค่าส่งคืนอย่างไร &&สิ่งที่สำคัญก็คือว่ามันส่งกลับ
Nicol Bolas

1
หมายเหตุ: ค่าความแปรปรวนสามารถอยู่ทางซ้ายมือของสมการได้ - เช่นเดียวกับในX foo(); foo() = X;... ด้วยเหตุผลพื้นฐานนี้ฉันไม่สามารถทำตามคำตอบที่ดีเลิศดังกล่าวได้จนถึงตอนจบเพราะคุณสร้างความแตกต่างระหว่าง xvalue ใหม่และ prvalue แบบเก่าบนพื้นฐานของข้อเท็จจริงที่ว่ามันสามารถอยู่บน lhs
Dan Nissenbaum

1
Xเป็นคลาส X foo();เป็นการประกาศฟังก์ชั่นและfoo() = X();เป็นบรรทัดของรหัส (ฉันทิ้งวงเล็บชุดที่สองไว้ในfoo() = X();ความคิดเห็นด้านบน) สำหรับคำถามที่ฉันเพิ่งโพสต์ด้วยการใช้งานที่ไฮไลต์นี้ให้ดูstackoverflow.com/questions/15482508/…
Dan Nissenbaum

1
@DanNissenbaum "xvalue ไม่สามารถอยู่ทางซ้ายมือของนิพจน์การมอบหมาย" - ทำไมล่ะ? ดูideone.com/wyrxiT
Mikhail

1
คำตอบที่กระจ่างแจ้ง นี่คือคำตอบที่ดีที่สุดอย่างไม่ต้องสงสัยที่นี่ มันให้เหตุผลของการแนะนำหมวดหมู่ค่าใหม่และสิ่งที่เกิดขึ้นก่อนหน้านี้
Nikos

136

IMHO คำอธิบายที่ดีที่สุดเกี่ยวกับความหมายของมันทำให้เราStroustrup + พิจารณาตัวอย่างของDánielSándorและMohan :

Stroustrup:

ตอนนี้ฉันเป็นห่วงอย่างจริงจัง เห็นได้ชัดว่าเรามุ่งหน้าไปทางตันหรือเป็นระเบียบหรือทั้งสองอย่าง ฉันใช้เวลากลางวันทำการวิเคราะห์เพื่อดูว่าคุณสมบัติใด (ของค่า) เป็นอิสระ มีเพียงสองคุณสมบัติอิสระ:

  • has identity - เช่นและที่อยู่, ตัวชี้, ผู้ใช้สามารถกำหนดได้ว่าสำเนาสองชุดเหมือนกันหรือไม่ ฯลฯ
  • can be moved from - เช่นเราได้รับอนุญาตให้ออกไปยังแหล่งที่มาของ "สำเนา" ในบางสถานการณ์ แต่ไม่ถูกต้อง

สิ่งนี้ทำให้ฉันสรุปได้ว่ามีค่าสามชนิด (ใช้เคล็ดลับการจดบันทึก regex ของการใช้อักษรตัวใหญ่เพื่อระบุค่าลบ - ฉันรีบ):

  • iM: มีตัวตนและไม่สามารถย้ายได้
  • im: มีข้อมูลประจำตัวและสามารถย้ายจาก (เช่นผลลัพธ์ของการส่งค่า lvalue ไปเป็นการอ้างอิงค่า rvalue)
  • Im: ไม่มีตัวตนและสามารถย้ายได้

    ความเป็นไปได้ที่สี่IM, (ไม่มีตัวตนและไม่สามารถเคลื่อนย้ายได้) ไม่มีประโยชน์ในC++(หรือฉันคิดว่า) ในภาษาอื่น

นอกเหนือจากการจำแนกประเภทของค่าพื้นฐานทั้งสามนี้แล้วเรายังมีข้อสรุปทั่วไปสองประการที่ชัดเจนซึ่งสอดคล้องกับคุณสมบัติอิสระทั้งสอง:

  • i: มีตัวตน
  • m: สามารถย้ายได้จาก

นี่ทำให้ฉันวางแผนภาพนี้บนกระดาน: ป้อนคำอธิบายรูปภาพที่นี่

การตั้งชื่อ

ฉันสังเกตเห็นว่าเรามีอิสระในการ จำกัด เพียงชื่อ: สองจุดทางด้านซ้าย (ติดป้ายiMและi) เป็นสิ่งที่คนที่มีพิธีเรียกมากหรือน้อยlvaluesและสองจุดทางด้านขวา (ติดป้าย mและIm) เป็นคนที่มีระเบียบมากขึ้นหรือน้อยลง rvaluesได้เรียกว่า สิ่งนี้จะต้องสะท้อนให้เห็นในการตั้งชื่อของเรา นั่นคือ "ขา" ด้านซ้ายของWควรมีชื่อที่เกี่ยวข้องกับlvalueและ "ขา" ด้านขวาWควรมีชื่อที่เกี่ยวข้องกับrvalue.ฉันทราบว่าการอภิปราย / ปัญหาทั้งหมดนี้เกิดขึ้นจากการแนะนำการอ้างอิงค่า rvalue และการย้ายความหมาย ความคิดเหล่านี้ก็ไม่ได้มีอยู่ในโลกของ Strachey ประกอบด้วยเพียงและrvalues lvaluesมีคนสังเกตุว่าความคิดนั้น

  • ทุกvalueอย่างเป็นlvalueหรือrvalue
  • lvalueไม่ได้เป็นrvalueและrvalueไม่ได้เป็นlvalue

มีการฝังลึกในจิตสำนึกของเราคุณสมบัติที่มีประโยชน์มากและร่องรอยของการแบ่งขั้วนี้สามารถพบได้ทั่วทั้งร่างมาตรฐาน เราทุกคนตกลงกันว่าเราควรจะรักษาคุณสมบัติเหล่านั้นไว้ (และทำให้แม่นยำ) สิ่งนี้ จำกัด ตัวเลือกการตั้งชื่อของเราเพิ่มเติม ฉันสังเกตว่าถ้อยคำในห้องสมุดมาตรฐานใช้rvalueเพื่อหมายถึงm(ความหมายทั่วไป) ดังนั้นเพื่อรักษาความคาดหวังและข้อความของห้องสมุดมาตรฐานจุดล่างขวาของWควรจะตั้งชื่อ rvalue.

สิ่งนี้นำไปสู่การอภิปรายที่เน้นการตั้งชื่อ ก่อนอื่นเราต้องตัดสินใจว่าlvalue.ควรจะมีความlvalueหมายiMหรือลักษณะทั่วไปiหรือไม่ นำโดย Doug Gregor เราแสดงรายการสถานที่ในถ้อยคำภาษาหลักที่คำlvalueนั้นมีคุณสมบัติเหมาะสมในการแปลความหมายอย่างใดอย่างหนึ่ง รายการที่ถูกสร้างขึ้นมาและในกรณีส่วนใหญ่และในที่สุดหากินข้อความ / เปราะ หมายถึงปัจจุบันlvalue iMนี่คือความหมายแบบคลาสสิกของ lvalue เพราะ "ในสมัยก่อน" ไม่มีอะไรขยับ; เป็นความคิดที่ในนิยายmove C++0xนอกจากนี้การตั้งชื่อจุดบนสุดของW lvalueทรัพย์สินทำให้เราเห็นว่าทุกค่าเป็นlvalueหรือrvalueไม่ แต่ไม่ใช่ทั้งสองอย่าง

ดังนั้นจุดซ้ายบนของWคือlvalueและจุดล่างขวาคือrvalue.อะไรที่ทำให้จุดล่างซ้ายและขวาบน จุดล่างซ้ายเป็นลักษณะทั่วไปของ lvalue แบบดั้งเดิมทำให้สามารถเคลื่อนที่ได้ ดังนั้นgeneralized lvalue.เราจึงตั้งชื่อมันว่า glvalue.คุณสามารถพูดคลุมเครือเกี่ยวกับตัวย่อ แต่ (ฉันคิดว่า) ไม่ใช่ด้วยตรรกะ เราสันนิษฐานว่าในการใช้งานอย่างจริงจังgeneralized lvalue จะมีตัวย่ออยู่แล้วดังนั้นเราจึงควรทำทันที (หรืออาจเกิดความสับสน) จุดบนขวาของ W นั้นกว้างกว่าด้านล่างขวา (ตอนนี้เท่าที่เคยเรียกrvalue) จุดนั้นแสดงถึงความคิดบริสุทธิ์ดั้งเดิมของวัตถุที่คุณสามารถเคลื่อนย้ายได้เนื่องจากไม่สามารถอ้างถึงได้อีก (ยกเว้นผู้ทำลาย) ฉันชอบวลีที่specialized rvalueตรงข้ามกับgeneralized lvalueแต่pure rvalueตัวย่อจะprvalueชนะ (และอาจถูกต้องดังนั้น) ดังนั้นขาซ้ายของ W คือlvalueและ glvalueและขาขวาเป็นprvalueและโดยrvalue.บังเอิญทุกค่าเป็น glvalue หรือ prvalue แต่ไม่ใช่ทั้งสองอย่าง

ใบนี้อยู่ตรงกลางด้านบนของW: im; นั่นคือค่าที่มีตัวตนและสามารถเคลื่อนย้ายได้ เราไม่มีสิ่งใดที่จะนำทางเราให้เป็นชื่อที่ดีสำหรับสัตว์ลึกลับเหล่านั้น พวกเขามีความสำคัญต่อคนที่ทำงานกับข้อความมาตรฐาน (ฉบับร่าง) แต่ไม่น่าจะกลายเป็นชื่อครัวเรือน เราไม่พบข้อ จำกัด ที่แท้จริงในการตั้งชื่อเพื่อให้คำแนะนำแก่เราดังนั้นเราจึงเลือก 'x' สำหรับศูนย์กลางไม่ทราบแปลกประหลาด xpert เท่านั้นหรือแม้แต่อันดับ x

สตีฟแสดงผลิตภัณฑ์ขั้นสุดท้าย


14
ใช่มันจะดีกว่าที่จะอ่านข้อเสนอเดิมและการอภิปรายของ com C + + กว่ามาตรฐานถ้าคุณต้องการที่จะเข้าใจสิ่งที่พวกเขาหมายถึง: D
Ivan Kush

8
ตัวอักษรไม่มีตัวตนและไม่สามารถย้ายได้ พวกเขายังมีประโยชน์
DrPizza

ฉันแค่อยากจะอธิบายอะไรสักอย่าง int && f () {return 1; } และ MyClass && g () {return MyClass (); } คืนค่า xvalue ใช่ไหม แล้วฉันจะหาตัวตนของนิพจน์ f () ได้จากที่ไหน และ "g ();"? พวกเขามีตัวตนเพราะมีการแสดงออกในงบกลับที่หมายถึงวัตถุเดียวกันกับที่พวกเขาอ้างถึง - ฉันเข้าใจมันถูกต้องหรือไม่
DánielSándor

6
@DrPizza ตามมาตรฐาน: ตัวอักษรสตริงเป็นlvalues ตัวอักษรอื่น ๆ ทั้งหมดคือprvalues การพูดอย่างเคร่งครัดคุณสามารถโต้แย้งได้ว่าการพูดตัวอักษรที่ไม่ใช่สตริงควรจะเคลื่อนย้ายได้ แต่นั่นไม่ใช่วิธีการเขียนมาตรฐาน
Brian Vandenberg

59

บทนำ

ISOC ++ 11 (เป็นทางการ ISO / IEC 14882: 2011) เป็นเวอร์ชันล่าสุดของมาตรฐานของภาษาการเขียนโปรแกรม C ++ มันมีคุณสมบัติใหม่และแนวคิดเช่น:

  • การอ้างอิงค่า
  • xvalue, glvalue, หมวดค่าการแสดงออกของ prvalue
  • ย้ายความหมาย

หากเราต้องการที่จะเข้าใจแนวคิดของหมวดค่านิพจน์ใหม่เราต้องระวังว่ามีการอ้างอิง rvalue และ lvalue เป็นการดีกว่าที่จะทราบว่าค่า rvalues ​​สามารถส่งผ่านไปยังการอ้างอิงค่าที่ไม่ใช่ค่า const

int& r_i=7; // compile error
int&& rr_i=7; // OK

เราสามารถรับแนวคิดของหมวดหมู่ค่าได้ถ้าเราอ้างอิงส่วนย่อยที่ชื่อ Lvalues ​​และ rvalues ​​จากร่างการทำงาน N3337 (ร่างที่คล้ายกันมากที่สุดกับมาตรฐาน ISOC ++ 11 ที่เผยแพร่)

3.10 ค่าและ rvalues ​​[basic.lval]

1 นิพจน์ถูกจัดประเภทตามอนุกรมวิธานในรูปที่ 1

  • lvalue (เรียกว่าในอดีตเนื่องจาก lvalues ​​อาจปรากฏที่ด้านซ้ายมือของนิพจน์การมอบหมาย) จะกำหนดฟังก์ชันหรือวัตถุ [ตัวอย่าง: ถ้า E เป็นนิพจน์ประเภทตัวชี้ดังนั้น * E คือนิพจน์ lvalue ที่อ้างถึงวัตถุหรือฟังก์ชันที่จุด E นั้น เป็นอีกตัวอย่างหนึ่งผลลัพธ์ของการเรียกใช้ฟังก์ชันที่มีชนิดส่งคืนคือการอ้างอิง lvalue คือ lvalue - ตัวอย่าง]
  • ค่า xvalue (ค่า“ eXpiring”) ยังหมายถึงวัตถุซึ่งมักจะใกล้ถึงจุดสิ้นสุดของอายุการใช้งาน (ตัวอย่างเช่นทรัพยากรอาจถูกเคลื่อนย้าย) xvalue คือผลลัพธ์ของนิพจน์บางประเภทที่เกี่ยวข้องกับการอ้างอิง rvalue (8.3.2) [ตัวอย่าง: ผลลัพธ์ของการเรียกใช้ฟังก์ชันที่มีชนิดส่งคืนคือการอ้างอิง rvalue คือ xvalue - ตัวอย่าง]
  • glvalue (“ generalized” lvalue) คือ lvalue หรือ xvalue
  • rvalue (เรียกว่าในอดีตเนื่องจาก rvalues ​​สามารถปรากฏที่ด้านขวามือของนิพจน์การมอบหมาย) คือ xvalue
    วัตถุชั่วคราว (12.2) หรือ subobject หรือค่าที่ไม่
    เกี่ยวข้องกับวัตถุ
  • prvalue (“ บริสุทธิ์” rvalue) เป็น rvalue ที่ไม่ใช่ xvalue [ตัวอย่าง: ผลลัพธ์ของการเรียกใช้ฟังก์ชันที่มีประเภทส่งคืนไม่ใช่การ
    อ้างอิงคือ prvalue ค่าของตัวอักษรเช่น 12, 7.3e5 หรือ
    ความจริงก็เป็นที่แพร่หลาย - ตัวอย่าง]

ทุกการแสดงออกเป็นของหนึ่งในการจำแนกประเภทพื้นฐานในอนุกรมวิธานนี้: lvalue, xvalue หรือ prvalue คุณสมบัติของนิพจน์นี้เรียกว่าหมวดหมู่ค่า

แต่ฉันไม่แน่ใจเกี่ยวกับว่าส่วนย่อยนี้เพียงพอที่จะเข้าใจแนวคิดอย่างชัดเจนเพราะ "โดยปกติ" ไม่จริงทั่วไป "ใกล้ถึงจุดสิ้นสุดของอายุการใช้งาน" ไม่เป็นรูปธรรมจริงๆ "เกี่ยวข้องกับการอ้างอิงค่า rvalue" ไม่ชัดเจนจริง ๆ และ "ตัวอย่าง: ผลลัพธ์ของการเรียกใช้ฟังก์ชันที่มีชนิดส่งคืนคือการอ้างอิง rvalue คือ xvalue" ดูเหมือนงูกัดหางของมัน

หมวดหมู่ค่านิยมหลัก

ทุกนิพจน์เป็นของหมวดค่าหลักหนึ่งหมวด หมวดค่าเหล่านี้คือประเภท lvalue, xvalue และ prvalue

lvalues

นิพจน์ E เป็นของหมวดหมู่ lvalue ถ้าหาก E อ้างถึงเอนทิตีที่ ALREADY มีตัวตน (ที่อยู่ชื่อหรือนามแฝง) ที่ทำให้สามารถเข้าถึงได้นอก E

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}

xvalues

นิพจน์ E เป็นของประเภท xvalue ถ้าหากว่าเป็นเท่านั้น

- ผลลัพธ์ของการเรียกใช้ฟังก์ชันไม่ว่าโดยนัยหรือโดยชัดแจ้งซึ่งชนิดส่งคืนคือการอ้างอิง rvalue กับชนิดของวัตถุที่ส่งคืนหรือ

int&& f(){
    return 3;
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.

    return 0;
}

- การส่งไปยังการอ้างอิง rvalue กับชนิดของวัตถุหรือ

int main()
{
    static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
    std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).

    return 0;
}

- นิพจน์การเข้าถึงของสมาชิกคลาสที่กำหนดสมาชิกข้อมูลที่ไม่คงที่ของประเภทที่ไม่อ้างอิงซึ่งนิพจน์วัตถุนั้นเป็น xvalue หรือ

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.

    return 0;
}

- นิพจน์ตัวชี้ไปยังสมาชิกซึ่งตัวถูกดำเนินการตัวแรกคือ xvalue และตัวถูกดำเนินการตัวที่สองคือตัวชี้ไปยังข้อมูลสมาชิก

โปรดทราบว่าผลกระทบของกฎด้านบนคือชื่อการอ้างอิง rvalue ไปยังวัตถุนั้นถือว่าเป็น lvalues ​​และการอ้างอิง rvalue ที่ไม่มีชื่อไปยังวัตถุนั้นจะถือว่าเป็น xvalues การอ้างอิง rvalue ไปยังฟังก์ชั่นจะถือว่าเป็น lvalues ​​ไม่ว่าจะตั้งชื่อหรือไม่ก็ตาม

#include <functional>

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
    As&& rr_a=As();
    rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
    std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.

    return 0;
}

prvalues

นิพจน์ E เป็นของหมวด prvalue ถ้าหาก E นั้นไม่ใช่ของ lvalue หรือในหมวด xvalue

struct As
{
    void f(){
        this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
    }
};

As f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.

    return 0;
}

หมวดหมู่ค่าผสม

มีสองประเภทค่าผสมที่สำคัญเพิ่มเติม หมวดค่าเหล่านี้คือประเภท rvalue และ glvalue

rvalues

นิพจน์ E เป็นของหมวด rvalue ถ้าหาก E นั้นเป็นของประเภท xvalue หรือไปยังหมวด prvalue

โปรดทราบว่าคำจำกัดความนี้หมายความว่านิพจน์ E เป็นของหมวด rvalue ถ้าหาก E อ้างถึงเอนทิตีที่ไม่มีตัวตนใด ๆ ที่ทำให้เข้าถึงได้นอก E YET

glvalues

นิพจน์ E เป็นของหมวดหมู่ glvalue ถ้าหาก E นั้นเป็นของประเภท lvalue หรือไปยังหมวดหมู่ xvalue

กฎปฏิบัติ

Scott Meyer ได้เผยแพร่กฎของหัวแม่มือที่มีประโยชน์มากเพื่อแยกความแตกต่าง rvalues ​​จาก lvalues

  • หากคุณสามารถใช้ที่อยู่ของนิพจน์นิพจน์นั้นจะเป็น lvalue
  • หากประเภทของการแสดงออกคือการอ้างอิง lvalue (เช่น T & หรือ const T &, ฯลฯ ) การแสดงออกนั้นเป็น lvalue
  • มิฉะนั้นการแสดงออกเป็น rvalue แนวคิด (และโดยทั่วไปแล้วในความเป็นจริง) rvalues ​​จะสอดคล้องกับวัตถุชั่วคราวเช่นสิ่งที่ส่งคืนจากฟังก์ชันหรือสร้างขึ้นผ่านการแปลงประเภทโดยนัย ค่าตัวอักษรส่วนใหญ่ (เช่น 10 และ 5.3) ก็มีค่าเช่นกัน

3
ตัวอย่างทั้งหมดสำหรับ lvalues ​​และตัวอย่างทั้งหมดสำหรับ xvalues ​​เป็นตัวอย่างสำหรับ glvalues ​​เช่นกัน ขอบคุณสำหรับการแก้ไข!
DánielSándor

1
คุณพูดถูก หมวดหมู่ค่าหลักสามหมวดนั้นพอเพียง Rvalue ไม่จำเป็นเช่นกัน ฉันคิดว่า rvalue และ glvalue อยู่ในมาตรฐานเพื่อความสะดวก
DánielSándor

1
มีช่วงเวลาที่ยากลำบากในการทำความเข้าใจstruct As{void f(){this;}}กับthisตัวแปรนี้ ฉันคิดว่าthisควรจะเป็น lvalue จนกว่ามาตรฐาน 9.3.2 จะพูดว่า: ในเนื้อความของฟังก์ชั่นสมาชิกแบบคงที่ (9.3) คำหลักนี้เป็นนิพจน์ทั่วไป
r0ng

3
@ r0ng thisเป็น prvalue แต่*thisเป็น lvalue
Xeverous

1
"www" ไม่ได้มีที่อยู่เดียวกันเสมอไป มันเป็น lvalue เพราะมันเป็นอาร์เรย์
เก่ง

35

หมวดหมู่ของ C ++ 03 นั้นมีข้อ จำกัด เกินไปที่จะบันทึกการแนะนำการอ้างอิง rvalue อย่างถูกต้องในแอตทริบิวต์ของนิพจน์

ด้วยการแนะนำของพวกเขามันก็บอกว่าการอ้างอิง rvalue ที่ไม่มีชื่อประเมินให้เป็น rvalue เช่นที่ความละเอียดเกินพิกัดจะชอบการอ้างอิงการอ้างอิง rvalue ซึ่งจะทำให้มันเลือกย้ายก่อสร้างมากกว่าการก่อสร้างคัดลอก แต่พบว่าสิ่งนี้ทำให้เกิดปัญหารอบตัวเช่นกับประเภทไดนามิกและคุณสมบัติ

ในการแสดงสิ่งนี้ให้พิจารณา

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}

บนแบบร่างก่อน xvalue สิ่งนี้ได้รับอนุญาตเพราะใน C ++ 03 ค่าของประเภทที่ไม่ใช่คลาสจะไม่ผ่านการรับรอง cv แต่มันก็มีจุดมุ่งหมายที่constจะนำไปใช้ในกรณี rvalue อ้างอิงเพราะที่นี่เราจะหมายถึงวัตถุ (= หน่วยความจำ!) และลดลงจาก const rvalues ไม่ใช่ระดับเป็นส่วนใหญ่ด้วยเหตุผลว่าไม่มีรอบวัตถุ

ปัญหาสำหรับประเภทไดนามิกมีลักษณะคล้ายกัน ใน C ++ 03 ค่าของประเภทคลาสมีชนิดไดนามิกที่รู้จัก - เป็นชนิดสแตติกของนิพจน์นั้น เนื่องจากมีวิธีอื่นคุณต้องมีการอ้างอิงหรือการอ้างอิงซึ่งประเมินค่าเป็น lvalue นั่นไม่เป็นความจริงกับการอ้างอิงค่า rvalue ที่ไม่มีชื่อ แต่ก็สามารถแสดงพฤติกรรมที่หลากหลายได้ ดังนั้นเพื่อแก้ปัญหา

  • อ้างอิง rvalue ชื่อกลายเป็นxvalues พวกเขาสามารถมีคุณสมบัติและอาจมีประเภทแบบไดนามิกที่แตกต่างกัน พวกเขาทำเช่นเดียวกับที่ตั้งใจไว้ชอบการอ้างอิง rvalue ในระหว่างการบรรทุกเกินพิกัดและจะไม่ผูกกับการอ้างอิง lvalue ที่ไม่ใช่ const

  • สิ่งที่ก่อนหน้านี้เป็น rvalue (ตัวอักษรวัตถุที่สร้างขึ้นโดยปลดเปลื้องประเภทที่ไม่ใช่อ้างอิง) ตอนนี้กลายเป็นprvalue พวกเขามีการตั้งค่าเช่นเดียวกับค่าในระหว่างการบรรทุกเกินพิกัด

  • สิ่งที่ก่อนหน้านี้คือ lvalue ยังคงเป็น lvalue

และมีการจัดกลุ่มสองกลุ่มเพื่อจับภาพผู้ที่มีคุณสมบัติและสามารถมีประเภทไดนามิกที่แตกต่างกัน ( glvalues ) และกลุ่มที่มีการโหลดมากเกินไปชอบการรวมการอ้างอิง rvalue ( rvalues )


1
คำตอบนั้นสมเหตุสมผลอย่างเห็นได้ชัด xvalue เป็นเพียง rvalue ซึ่งสามารถพิมพ์ด้วยคุณสมบัติ CV และไดนามิกได้!
แกนด์

26

ผมได้ต่อสู้กับเรื่องนี้มาเป็นเวลานานจนฉันมาข้ามคำอธิบาย cppreference.com ของหมวดมูลค่า

จริงๆแล้วมันค่อนข้างง่าย แต่ฉันพบว่ามันมักจะอธิบายในวิธีที่ยากต่อการจดจำ ที่นี่มีการอธิบายแผนผังอย่างมาก ฉันจะอ้างอิงบางส่วนของหน้า:

หมวดหมู่หลัก

หมวดหมู่ค่าหลักสอดคล้องกับคุณสมบัติสองประการของนิพจน์:

  • มีตัวตน : เป็นไปได้ที่จะตรวจสอบว่าการแสดงออกหมายถึงเอนทิตีเดียวกันกับการแสดงออกอื่นเช่นโดยการเปรียบเทียบที่อยู่ของวัตถุหรือฟังก์ชั่นที่พวกเขาระบุ (ได้โดยตรงหรือโดยอ้อม);

  • สามารถย้ายได้จาก : ตัวสร้างย้ายตัวดำเนินการกำหนดค่าการย้ายหรือฟังก์ชั่นโอเวอร์โหลดของฟังก์ชันอื่นที่ใช้ความหมายของการย้ายสามารถผูกกับนิพจน์ได้

นิพจน์ที่:

  • มีตัวตนและไม่สามารถเคลื่อนย้ายจากที่เรียกว่าการแสดงออก lvalue ;
  • มีตัวตนและสามารถย้ายจากที่เรียกว่าการแสดงออก xvalue ;
  • ไม่มีตัวตนและสามารถย้ายจากที่เรียกว่าการแสดงออกที่แพร่หลาย ;
  • ไม่มีตัวตนและไม่สามารถย้ายจากไม่ได้ใช้

lvalue

นิพจน์ lvalue ("ค่าซ้าย") คือนิพจน์ที่มีเอกลักษณ์และไม่สามารถย้ายได้

rvalue (จนถึง C ++ 11), prvalue (ตั้งแต่ C ++ 11)

นิพจน์ prvalue ("pure rvalue") คือนิพจน์ที่ไม่มีเอกลักษณ์และสามารถย้ายได้

Xvalue

นิพจน์ xvalue ("ค่าที่หมดอายุ") คือนิพจน์ที่มีเอกลักษณ์และสามารถย้ายได้

glvalue

นิพจน์ glvalue ("generalized lvalue") คือนิพจน์ที่เป็น lvalue หรือ xvalue มันมีเอกลักษณ์ มันอาจจะหรืออาจจะไม่ถูกย้ายจาก

rvalue (ตั้งแต่ C ++ 11)

นิพจน์ rvalue ("ค่าที่ถูกต้อง") คือนิพจน์ที่เป็น prvalue หรือ xvalue มันสามารถเคลื่อนย้ายจาก อาจมีหรือไม่มีตัวตน


1
ในหนังสือบางเล่มค่า x จะแสดงให้เห็นว่า x มาจาก "ผู้เชี่ยวชาญ" หรือ "ยอดเยี่ยม"
noɥʇʎԀʎzɐɹƆ

และที่สำคัญรายการตัวอย่างที่ครอบคลุมทั้งหมดของพวกเขา
Ciro Santilli 冠状病毒审查六四事件法轮功

19

หมวดหมู่ใหม่เหล่านี้เกี่ยวข้องกับหมวด rvalue และ lvalue ที่มีอยู่ได้อย่างไร

C ++ 03 lvalue ยังคงเป็น C ++ 11 lvalue ในขณะที่ C ++ 03 rvalue เรียกว่า prvalue ใน C ++ 11


14

ภาคผนวกหนึ่งของคำตอบที่ดีเลิศข้างต้นในจุดที่ทำให้ฉันสับสนแม้หลังจากที่ฉันได้อ่าน Stroustrup และคิดว่าฉันเข้าใจความแตกต่างของค่า rvalue / lvalue เมื่อคุณเห็น

int&& a = 3,

มันเป็นเรื่องที่ดึงดูดมากที่จะอ่านint&&เป็นประเภทและสรุปว่านั่นaเป็นค่านิยม มันไม่ใช่:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

aมีชื่อและเป็น ipso พฤตินัย lvalue อย่าคิดว่า&&เป็นส่วนหนึ่งของประเภทa; มันเป็นเพียงบางสิ่งที่จะบอกคุณว่าอะไรที่aได้รับอนุญาตให้ผูกมัด

เรื่องนี้โดยเฉพาะอย่างยิ่งสำหรับการT&&ขัดแย้งประเภทในการก่อสร้าง ถ้าคุณเขียน

Foo::Foo(T&& _t) : t{_t} {}

คุณจะคัดลอกลงใน_t tคุณต้องการ

Foo::Foo(T&& _t) : t{std::move(_t)} {}ถ้าคุณต้องการย้าย ผู้แปลของฉันจะเตือนฉันไหมเมื่อฉันออกไปmove!


1
ฉันคิดว่าคำตอบนี้สามารถชี้แจงได้ "สิ่งที่aได้รับอนุญาตให้ผูกกับ": แน่นอน แต่ในบรรทัดที่ 2 และ 3 ตัวแปรของคุณคือ c & b และไม่ใช่สิ่งที่ผูกติดอยู่กับประเภทของaสิ่งที่ไม่เกี่ยวข้องที่นี่ใช่ไหม เส้นจะเหมือนกันถ้ามีการประกาศa int aความแตกต่างหลักที่แท้จริงที่นี่คือในบรรทัดที่ 1 a ไม่จำเป็นต้องconstผูกกับ 3
เฟลิกซ์ Dombek

12

เนื่องจากคำตอบก่อนหน้านี้ครอบคลุมทฤษฎีที่อยู่เบื้องหลังหมวดค่าอย่างละเอียดมีเพียงอีกสิ่งหนึ่งที่ฉันต้องการเพิ่ม: คุณสามารถเล่นกับมันและทดสอบได้

สำหรับการทดลองแบบใช้มือบางอย่างกับหมวดค่าคุณสามารถใช้ตัวระบุแบบลดค่าประเภท พฤติกรรมของมันมีความแตกต่างอย่างชัดเจนระหว่างสามหมวดหมู่ค่าหลัก (xvalue, lvalue และ prvalue)

การใช้ตัวประมวลผลล่วงหน้าช่วยให้เราพิมพ์บางอย่าง ...

หมวดหมู่หลัก:

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value

ประเภทผสม:

#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
#define IS_RVALUE(X) (IS_PRVALUE(X) || IS_XVALUE(X))

ตอนนี้เราสามารถทำซ้ำ (เกือบ) ทุกตัวอย่างจากcppreference กับประเภทค่า

นี่คือตัวอย่างบางส่วนที่มี C ++ 17 (สำหรับ terse static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc 9.1. Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

หมวดหมู่ผสมนั้นเป็นเรื่องที่น่าเบื่อเมื่อคุณค้นพบหมวดหมู่หลัก

สำหรับตัวอย่างเพิ่มเติมบางคน (และการทดลอง) ตรวจสอบต่อไปการเชื่อมโยงบนสำรวจคอมไพเลอร์ อย่ารำคาญที่จะอ่านแอสเซมบลี ฉันเพิ่มคอมไพเลอร์จำนวนมากเพื่อให้แน่ใจว่ามันทำงานได้กับคอมไพเลอร์ทั่วไปทั้งหมด


ผมคิดว่า#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)ที่จริงควรจะ#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))เป็นอย่างอื่นมองสิ่งที่เกิดขึ้นถ้าคุณทั้งสอง&& IS_GLVALUE
Gabriel Devillers
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.