ما الذي تخفيه الحروف l، r، x، gl و pr؟ فهم فئات القيم في C++
\n\nإذا كنت قد كتبت يومًا كودًا بلغة C++ وحصلت على خطأ مثل "cannot bind non-const lvalue reference to an rvalue"، فقد واجهت هذا "السحر". فئات القيم — lvalue، rvalue، xvalue، glvalue و prvalue — ليست مجرد تصنيف أكاديمي. إنها الأساس الذي تقوم عليه دلالات النقل (move semantics) والنقل المثالي (perfect forwarding).
دعنا نفهم مرة واحدة وإلى الأبد ما يعنيه كل حرف، وكيفية التمييز بينها عمليًا، ولماذا بدون هذه المعرفة لا يمكن كتابة كود C++ حديث وفعال.
\n\nتاريخ موجز: من أين أتت هذه المصطلحات الخمسة؟
\n\nفي البداية، في زمن C، كان هناك مفهومان فقط: lvalue (القيمة اليسرى — ما يمكن أن يكون على يسار عملية الإسناد) وrvalue (القيمة اليمنى — ما يكون على اليمين). كان الأمر بسيطًا: المتغير هو lvalue، والرقم هو rvalue.
\n\nمع ظهور C++11 ودلالات النقل، لم يعد هذا التصور البسيط كافيًا. ظهرت مراجع rvalue (&&) والحاجة إلى التمييز بين الكائنات المؤقتة "المحتضرة". هكذا وُلد التصنيف الجديد، الذي يصفه معيار C++ من خلال خمس فئات.
ألقِ نظرة على التسلسل الهرمي (رسم بياني نصي):
\n\n expression
/ \\\
glvalue rvalue
/ \\ / \\\
lvalue xvalue prvalue\n\nاحفظ هذه الصورة — فهي المفتاح لكل شيء.
\n\nlvalue (left value) — الكائنات "الباقية"
\n\nlvalue هو تعبير يشير إلى كائن له عنوان في الذاكرة. هذا الكائن لن يختفي بعد انتهاء التعبير. يمكنك أخذ عنوانه، وتعديله، وسيستمر في الوجود.
\n\nأمثلة على lvalue:
\n- \n
- اسم المتغير:
int x = 5; x— هو lvalue. \n - المؤشر المُشار إليه:
*ptr. \n - مرجع إلى متغير:
int& ref = x; ref. \n - النص الحرفي:
"hello"(نعم، هو lvalue، وإن كان ثابتًا). \n
الكود:
\nint main() {\n int a = 10;\n int* p = &a; // أخذ العنوان — صحيح، a هو lvalue\n int& ref = a; // مرجع إلى lvalue\n\n // &(a + 1); // خطأ! a+1 هو rvalue، لا يوجد عنوان\n return 0;\n}\n\nrvalue (right value) — القيم "التي تُستخدم لمرة واحدة"
\n\nrvalue هو تعبير ليس له عنوان دائم في الذاكرة. إنه موجود فقط في لحظة حساب التعبير. لا يمكنك أخذ عنوانه (باستثناءات نادرة).
\n\nتشمل rvalue:
\n- \n
- الحرفيات (باستثناء النصوص):
42،3.14f،true. \n - نتائج العمليات الحسابية:
a + b. \n - الكائنات المؤقتة:
std::string("temp"). \n - تعبيرات لامدا. \n
الكود:
\nint foo() { return 5; }\n\nint main() {\n int x = 42; // 42 — rvalue\n int y = x + 10; // x+10 — rvalue\n // int* p = &(x + 10); // خطأ! لا يمكن أخذ عنوان rvalue\n return 0;\n}\n\nprvalue (pure rvalue) — rvalue "النقية"
\n\nprvalue هو rvalue الكلاسيكي من C++98. إنها قيمة مؤقتة ليس لها اسم ولا ترتبط بذاكرة. prvalue هو ما نسميه عادةً rvalue في الحديث العام.
\n\nأمثلة على prvalue:
\n- \n
- الأرقام، التعدادات، نتائج العوامل. \n
- استدعاء دالة تُرجع غير مرجع:
std::to_string(123). \n - كائن مؤقت تم إنشاؤه صراحةً:
MyClass(). \n
xvalue (expiring value) — الكائنات "المحتضرة"
\n\nهنا يبدأ الجزء المثير للاهتمام. xvalue هو كائن سيتم تدميره قريبًا، ولكن لا يزال من الممكن "إنقاذ" موارده (نقلها). هذا هو الجس