ترتبط فئات التعبيرات ، مثل lvalue و rvalue ، بالمفاهيم النظرية الأساسية للغة C ++ أكثر من الجوانب العملية لاستخدامها. لهذا السبب ، فإن العديد من المبرمجين ذوي الخبرة لديهم فكرة غامضة عما يعنونه. سأحاول في هذه المقالة شرح معنى هذه المصطلحات بأبسط ما يمكن ، مع تخفيف النظرية بأمثلة عملية. سأبدي تحفظًا على الفور: لا تدعي المقالة أنها تقدم الوصف الأكثر اكتمالًا وصرامة لفئات التعبيرات ، للحصول على تفاصيل أوصي بالاتصال بالمصدر مباشرةً: معيار لغة C ++.
تحتوي المقالة على الكثير من المصطلحات باللغة الإنجليزية ، ويرجع ذلك إلى حقيقة أن بعضها يصعب ترجمته إلى الروسية ، بينما يتم ترجمة الآخرين بمصادر مختلفة بطرق مختلفة. لذلك ، سأشير غالبًا إلى المصطلحات الإنجليزية ، مع التركيز عليها بخط مائل .
قليلا من التاريخ
ظهر المصطلحان lvalue و rvalue في C. تجدر الإشارة إلى أن الارتباك قد تم وضعه في المصطلحات في البداية ، لأنها تشير إلى التعبيرات وليس إلى القيم. تاريخياً ، القيمة هي ما يمكن تركه من مشغل المهمة ، والقيمة هي ما يمكن أن يكون صحيحًا فقط.
lvalue = rvalue; 
ومع ذلك ، فإن مثل هذا التعريف يبسط ويشوه الجوهر. تعريف C89 المعيار lvalue كمحدد موقع كائن ، أي كائن ذو موقع ذاكرة محدد. وفقًا لذلك ، تم تضمين كل ما لا يتوافق مع هذا التعريف في فئة rvalue .
Bjarn يسارع إلى الإنقاذ
في لغة C ++ ، تطورت مصطلحات فئات التعبير بقوة كبيرة ، خاصة بعد اعتماد معيار C ++ 11 ، الذي قدم مفاهيم روابط rvalue ونقل دلالات الألفاظ . تم وصف تاريخ ظهور مصطلحات جديدة بشكل مثير للاهتمام في مقالة Straustrup لمصطلح القيمة "جديد" .
تعتمد المصطلحات الجديدة والأكثر صرامة على خواصتين:
- وجود الهوية ( الهوية ) - أي ، بعض المعلمات التي يمكن من خلالها فهم ما إذا كان تعبيران يشيران إلى نفس الكيان أم لا (على سبيل المثال ، عنوان في الذاكرة) ؛
- القدرة على التحرك ( يمكن نقلها من ) - يدعم دلالات الحركة.
يتم تعبير تعبيرات التعبير عن الهوية تحت مصطلح glvalue ( القيم المعممة ) ، وتسمى تعبيرات التجوال rvalue . حددت مجموعات من هاتين الخاصيتين 3 فئات رئيسية من التعبيرات:
|  | لديك هوية | خالية من الهوية | 
|---|
| لا يمكن نقلها | القيمة | - | 
| يمكن نقلها | xvalue | القيمة | 
في الواقع ، قدم C ++ 17 Standard مفهوم elition copy - إضفاء الطابع الرسمي على المواقف حيث يمكن للمترجم وينبغي عليه تجنب نسخ الكائنات ونقلها. في هذا الصدد ، قد لا يتم نقل القيمة بالضرورة. التفاصيل والأمثلة يمكن العثور عليها هنا . ومع ذلك ، لا يؤثر هذا على فهم المخطط العام لفئات التعبيرات.
في معيار C ++ الحديث ، يتم تقديم هيكل الفئة في شكل مثل هذا المخطط:

دعنا نفحص بشكل عام خصائص الفئات ، وكذلك تعبيرات اللغة المضمنة في كل فئة من الفئات. لاحظ على الفور أن قوائم التعبيرات أدناه لكل فئة لا يمكن اعتبارها كاملة ؛ للحصول على معلومات أكثر دقة وتفصيلا ، يرجى الرجوع مباشرة إلى معيار C ++.
glvalue
التعبيرات في فئة glvalue لها الخصائص التالية:
- يمكن تحويلها ضمنيًا إلى القيمة .
- يمكن أن يكون متعدد الأشكال ، أي أن مفاهيم النوع الثابت والديناميكي لها معنى ؛
- لا يمكن أن يكون نوع الفراغ - وهذا يتبع مباشرة خاصية وجود هوية ، لأن تعبيرات نوع الفراغ لا توجد مثل هذه المعلمة التي تميزها عن بعضها البعض ؛
- يمكن أن يكون لها نوع غير مكتمل ، على سبيل المثال ، في شكل إعلان أمامي (إذا سمح بتعبير معين).
rvalue
التعبيرات في فئة rvalue لها الخصائص التالية:
- لا يمكنك الحصول على عنوان rvalue في الذاكرة - وهذا يأتي مباشرة من نقص خاصية الهوية ؛
- لا يمكن أن يكون على الجانب الأيسر من بيان مهمة أو مركب ؛
- يمكن استخدامها لتهيئة ارتباط ثابت أو قيمة منخفضة القيمة ، بينما يمتد عمر الكائن إلى عمر الارتباط ؛
- إذا تم استخدامه كوسيطة عند استدعاء دالة لها نسختان زائدا: أحدهما يقبل مرجع قيمة ثابت والآخر مرجع rvalue ، ثم يتم تحديد الإصدار الذي يقبل مرجع rvalue . هذه الخاصية تُستخدم لتنفيذ دلالات النقل :
 class A { public: A() = default; A(const A&) { std::cout << "A::A(const A&)\n"; } A(A&&) { std::cout << "A::A(A&&)\n"; } }; ......... A a; A b(a); //  A(const A&) A c(std::move(a)); //  A(A&&) 
تقنيًا ، A&& عبارة عن قيمة قيمة ويمكن استخدامها لتهيئة كل من مرجع القيمة الثابتة ومرجع القيمة . ولكن بفضل هذه الخاصية ، لا يوجد أي غموض ؛ يتم قبول خيار المنشئ الذي يقبل مرجع rvalue .
القيمة
خصائص:
- جميع خصائص glvalue (انظر أعلاه) ؛
- يمكنك أن تأخذ العنوان (باستخدام المشغل الأحادي المدمج &)؛
- يمكن أن تكون القيم القابلة للتعديل على الجانب الأيسر من مشغل التعيين أو مشغلي التعيين المركب ؛
- يمكن استخدامها لتهيئة إشارة إلى قيمة (ثابتة وغير ثابتة).
التعبيرات التالية تنتمي إلى فئة lvalue :
- اسم متغير أو وظيفة أو حقل فئة من أي نوع. حتى إذا كان المتغير مرجع rvalue ، فإن اسم هذا المتغير في التعبير هو lvalue ؛
 void func() {} ......... auto* func_ptr = &func; // :     auto& func_ref = func; // :     int&& rrn = int(123); auto* pn = &rrn; // :    auto& rn = rrn; // :  lvalue- 
- استدعاء دالة أو عامل التشغيل الزائد الذي يُرجع مرجع القيمة أو تعبير التحويل إلى نوع مرجع القيمة ؛
- عوامل تشغيل التعيين المضمنة ، عوامل تشغيل التعيين المركبة ( =،+=،/=، وما إلى ذلك) ، الزيادة المسبقة المضمنة والزيادة المسبقة (++a، ---b) ،--bمؤشر المؤشر المدمج (*p) ؛
- عامل التشغيل المدمج للوصول بواسطة الفهرس ( a[n]أوn[a]) ، عندما يكون أحد المعاملات هو صفيف القيمة ؛
- استدعاء دالة أو عبارة overloaded تقوم بإرجاع مرجع rvalue إلى دالة ؛
- سلسلة حرفية مثل "Hello, world!".
تختلف السلسلة الحرفية عن جميع القيم الحرفية الأخرى في لغة C ++ على وجه التحديد ، حيث إنها قيمة (وإن كانت غير قابلة للتغيير). على سبيل المثال ، يمكنك الحصول على عنوانه:
 auto* p = &”Hello, world!”; //   ,    
القيمة
خصائص:
- جميع خصائص rvalue (انظر أعلاه) ؛
- لا يمكن أن يكون متعدد الأشكال: أنواع التعبير الثابتة والديناميكية تتزامن دائمًا ؛
- لا يمكن أن يكون من نوع غير مكتمل (باستثناء نوع الفراغ ، ستتم مناقشة هذا أدناه) ؛
- لا يمكن أن يحتوي على نوع تجريدي أو أن يكون مجموعة من العناصر من نوع الملخص.
التعبيرات التالية تنتمي إلى فئة prvalue :
- حرفية (باستثناء السلسلة) ، على سبيل المثال 42،trueأوnullptr؛
- استدعاء دالة أو عامل التشغيل str.substr(1, 2)الذي يقوم بإرجاع غير مرجعية (str.substr(1, 2)أوstr1 + str2أوit++) أو تعبير تحويل إلى نوع غير مرجعي (على سبيل المثالstatic_cast<double>(x)أوstd::string{}،(int)42) ؛
- المدمج في الزيادة اللاحقة واللاحقة ( a++،b--) ، العمليات الرياضية المضمنة (a + b،a % b،a & b،a << b، إلخ) ، العمليات المنطقية المضمّنة (a && b،a || b!a، إلخ) ، عمليات المقارنة (a < b،a == b،a >= b، إلخ) ، العملية المضمنة لأخذ العنوان (&a)؛
- هذا المؤشر
- قائمة البند ؛
- معلمة قالب غير نمطية ، إذا لم تكن فئة ؛
- تعبير lambda ، على سبيل المثال [](int x){ return x * x; }[](int x){ return x * x; }.
xvalue
خصائص:
- جميع خصائص rvalue (انظر أعلاه) ؛
- جميع خصائص glvalue (انظر أعلاه).
أمثلة على تعبيرات xvalue :
- استدعاء دالة أو مشغل مضمن يقوم بإرجاع مرجع rvalue ، على سبيل المثال std :: move (x) ؛
وفي الحقيقة ، نتيجة لاستدعاء std :: move () ، لا يمكنك الحصول على عنوان في الذاكرة أو تهيئة رابط له ، ولكن في نفس الوقت ، يمكن أن يكون هذا التعبير متعدد الأشكال:
 struct XA { virtual void f() { std::cout << "XA::f()\n"; } }; struct XB : public XA { virtual void f() { std::cout << "XB::f()\n"; } }; XA&& xa = XB(); auto* p = &std::move(xa); //  auto& r = std::move(xa); //  std::move(xa).f(); //  “XB::f()” 
- عامل تشغيل مدمج للوصول بواسطة الفهرس ( a[n]أوn[a]) عندما يكون أحد المعاملات هو صفيف rvalue .
بعض الحالات الخاصة
مشغل فاصلة
بالنسبة لمشغل الفاصلة المدمجة ، تتطابق فئة التعبير دائمًا مع فئة التعبير للمعامل الثاني.
 int n = 0; auto* pn = &(1, n); // lvalue auto& rn = (1, n); // lvalue 1, n = 2; // lvalue auto* pt = &(1, int(123)); // , rvalue auto& rt = (1, int(123)); // , rvalue 
تعبيرات باطلة
تعتبر استدعاءات الوظائف التي تُرجع لاغية ، اكتب تعبيرات التحويل لاغية ، ورمي استثناءات تعبيرات prvalue ، ولكن لا يمكن استخدامها لتهيئة المراجع أو كوسائط للدالات .
عامل المقارنة الثلاثي
تعريف فئة التعبير a ? b : c a ? b : c - الحالة غير تافهة ، كل هذا يتوقف على فئات الوسيطتين الثانية والثالثة ( b و c ):
- إذا كانت bأوcمن النوع void ، فإن فئة ونوع التعبير بالكامل تتوافق مع الفئة ونوع الوسيطة الأخرى. إذا كانت كلتا الوسيطتين من النوع الفارغ ، فإن النتيجة هي قيمة من النوع الفراغ ؛
- إذا كان bوcglvalue من نفس النوع ، فإن النتيجة هي glvalue من نفس النوع ؛
- في حالات أخرى ، تكون النتيجة هي القيمة.
بالنسبة إلى العامل الثلاثي ، يتم تحديد عدد من القواعد التي يمكن من خلالها تطبيق التحويلات الضمنية على الوسيطتين b و c ، لكن هذا يتجاوز إلى حد ما نطاق المقالة ؛ إذا كنت مهتمًا ، أوصي بالرجوع إلى قسم المشغل Conditional [expr.cond] من المعيار.
 int n = 1; int v = (1 > 2) ? throw 1 : n; // lvalue, .. throw   void,    n ((1 < 2) ? n : v) = 2; //  lvalue,  ,   ((1 < 2) ? n : int(123)) = 2; //   , ..    prvalue 
الإشارات إلى حقول وأساليب الطبقات والهياكل
بالنسبة إلى تعبيرات النموذج am و p->m (هنا نتحدث عن المشغل المضمن -> ) ، تنطبق القواعد التالية:
- إذا كانت mعبارة عن عنصر تعداد أو طريقة فئة غير ساكنة ، فإن التعبير بأكمله يعتبر قيمة (على الرغم من أنه لا يمكن تهيئة الرابط بمثل هذا التعبير) ؛
- إذا كان arvalue وmهو حقل غير ساكن من نوع غير مرجعي ، فإن التعبير بأكمله ينتمي إلى فئة xvalue ؛
- خلاف ذلك هو قيمة.
بالنسبة إلى مؤشرات أعضاء الفصل ( a.*mp و p->*mp ) ، تتشابه القواعد:
- إذا كان mpهو مؤشر لطريقة الفصل ، فإن التعبير بأكمله يعتبر قيمة .
- إذا كان arvalue ، وmpهو مؤشر إلى حقل بيانات ، فإن التعبير بأكمله يشير إلى xvalue ؛
- خلاف ذلك هو قيمة.
بت الحقول
تعد حقول البت أداة ملائمة للبرمجة منخفضة المستوى ، ومع ذلك ، يقع تنفيذها إلى حد ما خارج الهيكل العام لفئات التعبير. على سبيل المثال ، يبدو أن الاتصال بحقل ما يمثل قيمة ، لأنه قد يكون موجودًا على الجانب الأيسر من مشغل المهمة. في الوقت نفسه ، لن تعمل على أخذ عنوان حقل البت أو تهيئة رابط غير ثابت بواسطتها. يمكنك تهيئة مرجع ثابت إلى حقل بت ، ولكن سيتم إنشاء نسخة مؤقتة من الكائن:
حقول البت [class.bit]
إذا كان المُهيئ لمرجع من النوع const T & هو قيمة تشير إلى حقل بت ، فإن المرجع يكون مرتبطًا بتهيئة مؤقتة للاحتفاظ بقيمة حقل البتات ؛ لا يرتبط المرجع بحقل البت مباشرة.
 struct BF { int f:3; }; BF b; bf = 1; // OK auto* pb = &b.f; //  auto& rb = bf; //  
بدلا من الاستنتاج
كما ذكرت في المقدمة ، لا يزعم الوصف أعلاه أنه مكتمل ، ولكنه يعطي فكرة عامة عن فئات التعبيرات. توفر طريقة العرض هذه فهمًا أفضل قليلاً لفقرات المعيار ورسائل خطأ المحول البرمجي.