تُسنَد فئات القيمة إلى تعبيرات C++ بناءً على نتائج تلك التعبيرات، ويمكن لهذه الفئات أن تؤثّر على تحليل التحميل الزائد (overload resolution) للدوالّ في C++، كما تحدّد خاصّيتين مهمَّتين ومنفصلتين حول التعابير، تحدد الأول منهما إذا كان للتعبير هوية (identity)، أي إذا كان يشير إلى كائن له اسمُ متغيّرٍ (variable name)، حتى لو لم يكن اسم المتغيّر مُضمَّنًا في التعبير. والثانية هي إذا كان يجوز النقل ضمنيًا (implicitly move) من قيمة التعبير، أو بشكل أدقّ، إذا كان التعبير سيرتبط بأنواع المعاملات ذات القيمة اليمينية (r-value parameter types) عند استخدامه كمُعامل دالة أم لا.
تُعرّف C++ ثلاث فئات تمثّل مجموعة من هذه الخصائص:
- lvalue - تعبير يساري (تعبيرات ذات هوّية، ولكن لا يمكن النقل منها).
- xvalue - تعبير مرتحل (تعبيرات ذات هوّية ويمكن النقل منها).
- prvalue - تعبير يميني خالص (تعبيرات بدون هوية ويمكن النقل منها).
لا تحتوي C++ على تعبيرات ليس لها هوية ولا يمكن النقل منها. من ناحية أخرى، تعرّف C++ فئتي قيمَة أخريين، كل منهما تعتمد حصرًا على إحدى هذه الخصائص:
- glvalue - تعبير يساري مُعمّم النوع (تعبيرات ذات هوية)
- rvalue - تعبير يميني (تعبيرات يمكن النقل منها).
ويمكن استخدام هذه كمجموعات لتصنيف الفئات السابقة. انظر هذا الرسم للتوضيح:
القيم اليمينيّة rvalue
التعبير اليمينيّ rvalue هو أيّ تعبير يمكن نقله ضمنيًا بغض النظر عما إذا كانت له هوية. وبتعبير أدق، يمكن استخدام التعبيرات اليمينيّة كوسائط للدوال التي تأخذ مُعاملات من النوع T &&
( يمثّل T
نوع التعبير expr
)، والتعبيرات اليمينية هي وحدها التي يمكن تمريرها كوسائط لمعاملات مثل هذه الدوالّ. أما في حال استخدام تعبير غير يميني فإنّّ تحليل التحميل الزائد سيختار أيّ دالّة لا تستخدم مرجع يمينيًا (rvalue reference)، وسيطلق خطأً في حال تعذّر العثور عليها.
تشمل فئة التعبيرات اليمينية حصرًا جميع تعبيرات xvalue
و prvalue
، وتحوّل دالّة المكتبة القياسية std::move
تعبيرًا غير يمينيّ بشكل صريح إلى تعبير يميني، إذ تحوّل التعبير إلى تعبير من الفئة xvalue
، فحتى لو كان التعبير يمينيا خالصًا ناقص الهوية (identity-less prvalue) من قبل، فإنّه سيكتسب هويةً -اسم معامِل الدالة- عبر تمريره كمُعامل إلى std::move
، ويصبح من الفئة xvalue. انظر المثال التالي:
std::string str("init"); //1 std::string test1(str); //2 std::string test2(std::move(str)); //3 str = std::string("new value"); //4 std::string &&str_ref = std::move(str); //5 std::string test3(str_ref); //6
يأخذ منشئ السلاسل النصية مُعاملًا واحدًا من النوع std::string&&
، ويُطلق على هذا المنشئ عادة "منشئ النقل" (move constructor)، لكن فئة القيمة الخاصة بـ str
ليست يمينيّة (بل يساريّة)، لذا لا يمكن استدعاء التحميل الزائد للمنشئ. وبدلاً من ذلك يُستدعي التحميل الزائد لـ const std::string&
، أي مُنشئ النسخ.
تتغير الأمور في السطر الثالث حيث تكون القيمة المُعادة من std::move
هي T&&
، إذ يمثّل T
النوع الأساسي للمُعامِل المُمرّر، لذا فإنّّ std::move(str)
تُعيد std::string&&
. واستدعاء الدالة الذي تكون قيمته المعادة مرجعًا يمينيًا (rvalue reference) يُعدُّ تعبيرًا يمينيًا، وتحديدًا من فئة xvalue
، لذا قد تستدعي منشئَ النقلِ للسلسلة النصية std::string
. بعد ذلك، أي بعد السطر الثالث، يكون النقل من str
قد تم، وصارت محتويات str
غير محددة.
يمرّر السطر 4 عنصرًا مؤقّتًا إلى مُعامل الإسناد الخاص بـ std::string
، الذي له تحميل زائد يأخذ std::string&&
، ويكون التعبير std::string("new value")
تعبيرًا يمينيًا (على وجه التحديد من الفئة prvalue)، لذا قد يستدعي التحميل الزائد. وعليه سيُنقل العنصر المؤقّت إلى str
مع استبدال المحتويات غير المُحدّدة بمحتويات محدّدة.
ينشئ السطر 5 مرجعًا يمينيًا مُسمّى (named rvalue reference) يحمل الاسم str_ref
ويشير إلى str
، وهنا تصبح فئات القيمة مُربكة. فرغم أن str_ref
ستكون مرجعًا يمينيًّا يشير إلى std::string
، إلا أن فئة قيمة التعبير str_ref
ليست يمينيّة بل يسارية، ولهذا لا يمكن للمرء استدعاء مُنشئ النقل الخاص بـ std::string
مع التعبير str_ref
. ولنفس السبب فإن السطر 6 ينسخ قيمة str
إلى test3
. وإذا أردنا نقله، سيتعيّن علينا توظيف std::move
مرّة أخرى.
القيمة المرتحلة xvalue
التعابير من فئة القيمة المرتحلة xvalue (أو eXpiring value) هي تعابير ذات هوية تمثّل كائنات يمكن النقل منها ضمنيًا. وفكرتها بشكل عام هي أنّ الكائنات التي تمثّلها هذه التعبيرات هي على وشك أن تُدمَّر، وبالتالي فإنّ النقل منها ضمنيًا سيكون مقبولًا.
انظر الأمثلة التالية على ذلك:
struct X { int n; }; extern X x; 4; // prvalue: ليس لديها هوية x; // lvalue x.n; // lvalue std::move(x); // xvalue std::forward<X&>(x); // lvalue X { 4 }; // prvalue: ليس لها هوية X { 4 }.n; // xvalue: ليس لها هوية وتشير إلى موارد يمكن إعادة استخدامها
تعبيرات القيمة اليمينية الخالصة prvalue
تعبير القيمة اليمينيّة الخالصة prvalue (أو pure-rvalue) هي تعبيرات تفتقر إلى الهوية، ويُستخدم تقييمها عادة لتهيئة كائن يمكن نقله ضمنيًا. هذه بعض الأمثلة على ذلك:
-
التعبيرات التي تمثّل كائنات مؤقّتة، مثل
std::string("123")
. - استدعاء دالة لا تعيد مرجعًا.
-
كائن حرفي (باستثناء السلاسل النصية الحرفية - إذ أنها يسارية)، مثل
1
أوtrue
أو0.5f
أو'a'
. - تعبيرات لامدا.
لا يمكن تطبيق معامل العنونة (addressof operator) المُضمّن (&
) على هذه التعبيرات.
تعبيرات القيمة اليسارية lvalue
التعبيرات اليسارية lvalue هي تعبيرات ذات هويّة لكن لا يمكن النقل منها ضمنيًا، وتشمل التعبيرات التي تتألّف من اسم متغيّر واسم دالّة والتعبيرات التي تستخدمها مُعاملات التحصيل (dereference) المُضمّنة والتعبيرات التي تشير إلى مراجع يسارية.
تكون القيمة اليسارية عادة مجرّد اسم، لكن يمكن أن تأتي بأشكال أخرى أيضًا:
struct X { … }; X x; // قيمة يسارية x X* px = &x; // px is an lvalue *px = X {}; // قيمة يمينيّة خالصة X{} هي قيمة يسارية، و *px X* foo_ptr(); // قيمة يمينيّة خالصة foo_ptr() X &foo_ref(); // قيمة يسارية foo_ref()
في حين أنّ معظم الكائنات الحرفية (على سبيل المثال 4
، 'x'
) هي تعبيرات يمينية خالصة، إلا أن السلاسل النصية الحرفية يسارية.
تعبيرات القيم اليسارية المُعمّمة glvalue
تعابير القيم اليسارية المُعمّمة glvalue (أو "generalized lvalue") هي التعابير التي ليس لها هوية بغض النظر عما إذا كان يمكن النقل منها أم لا.
وتشمل هذه الفئة تعبيرات القيم اليسارية (التعبيرات التي لها هوية ولكن لا يمكن النقل منها)، وتعبيرات القيمة المرتحلة xvalues (التعبيرات التي لها هوية، ويمكن النقل منها). بالمقابل، فهي لا تشمل القيم اليمينيةّ الخالصة prvalues (تعبيرات بدون هوية).
وإذا كان لتعبيرٍ ما اسم، فإنّّه يساريّ معمَّم glvalue:
struct X { int n; }; X foo(); X x; x; std::move(x); foo(); X {}; X {}.n;
في الشيفرة السابقة:
-
x
له اسم، وعليه يكون يساريًا معمَّمًا. -
(std::move(x
له اسم بما أننا ننقل منx
، وعليه يكون قيمة يسارية معمَّمةglvalue
لكن بما أننا نستطيع النقل منها فتكون قيمة مرتحلة وليست يسارية. -
()foo
ليس له اسم، فهو يميني خالص، وليس يساريا معمَّمًا. -
{} X
قيمة مؤقتة ليس لها اسم، لذا فالتعبير يميني خالص، وليس يساريا معمَّمًا. -
X {}.n
له اسم، لذا فهو يساري معمم، كما يمكن النقل منه، لذا فهو مرتحل وليس يساريًا معمَّمًا.
هذا الدرس جزء من سلسلة دروس عن C++.
ترجمة -بتصرّف- للفصل Chapter 74: Value Categories من كتاب C++ Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.